> For the complete documentation index, see [llms.txt](https://developers-apps-in-toss.toss.im/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developers-apps-in-toss.toss.im/documentation/api-and-sdk-en/unity/build/build-process.md).

# Build Pipeline

How a Unity project becomes distributable `.ait` Explains what the SDK does internally until it becomes a package.

> **Audience**: SDK contributors. If your goal is to build a game using the SDK, [Build profile](https://developers-apps-in-toss.toss.im/documentation/unity/build/build-profiles)and [Build customization](https://developers-apps-in-toss.toss.im/documentation/unity/build/build-customization)this is the document you need.

### 2-stage pipeline structure

The build is divided into the stage where Unity creates the WebGL output, and the stage where that output is rearranged into a web project and packaged with granite.

```
┌─────────────────────────────────────────────────────────────────────┐
│                        Entry Points                                 │
│  Menu: Build & Package  │  Build Window  │  Server Start/Restart    │
│  AppsInTossMenu.cs      │  AppsInTossBuildWindow.cs                 │
└─────────────┬───────────────────────────────────────────────────────┘
              │
              ▼
┌─────────────────────────────────────────────────────────────────────┐
│  AITConvertCore.DoExport(buildWebGL, doPackaging, cleanBuild,       │
│                          profile, profileName)                      │
│  or DoExportAsync(...)                                              │
└─────────────┬───────────────────────────────────────────────────────┘
              │
     ┌────────┴────────────────────────────────┐
     ▼                                         ▼
┌──────────────────────┐          ┌────────────────────────────────┐
│  Phase 1: WebGL Build │          │  Phase 2: Packaging            │
│  BuildWebGL()        │          │  GenerateMiniAppPackage()      │
│                      │          │  → AITPackageBuilder           │
│  - Init()            │          │    .PackageWebGLBuild()        │
│  - BuildPipeline     │          │                                │
│  - .ait-build-info   │          │  2a. Copy BuildConfig          │
│                      │          │  2b. Copy WebGL → public       │
│  Output: webgl/      │          │  2c. Replace placeholders      │
│                      │          │  2d. Insert loading screen     │
│                      │          │  2e. pnpm install              │
│                      │          │  2f. granite build             │
│                      │          │                                │
│                      │          │  Output: ait-build/dist/       │
└──────────────────────┘          └────────────────────────────────┘
```

#### Call matrix

| Entry point               | buildWebGL | doPackaging | cleanBuild |
| ------------------------- | ---------- | ----------- | ---------- |
| `Build & Package`         | `true`     | `true`      | `false`    |
| `Build & Package (clean)` | `true`     | `true`      | `true`     |
| `Deploy (Test)`           | `true`     | `true`      | `false`    |
| `Deploy (Production)`     | `true`     | `true`      | `true`     |
| `Dev Server Start`        | `true`     | `true`      | `false`    |
| `Restart Server`          | `true`     | `true`      | `false`    |
| `Restart (server-only)`   | —          | —           | —          |

> **Note**: `Restart (server-only)`is `DoExport`restarts only the granite process without calling it.

### Phase 0 initialization

#### Template synchronization

`AITTemplateManager.EnsureWebGLTemplatesExist`copies the SDK's WebGL templates into the project before building.

SDK template search order:

1. `Packages/im.toss.apps-in-toss-unity-sdk/WebGLTemplates/`
2. `Packages/com.appsintoss.miniapp/WebGLTemplates/`
3. Assembly path-based (`typeof(AITConvertCore).Assembly.Location` )

If it does not exist in the project, `Assets/WebGLTemplates/AITTemplate/`it copies everything; if it does exist, it updates based on markers to preserve user custom areas. Below **template merge timing** section.

#### Build settings

`AITBuildInitializer.Init`automatically configures Unity PlayerSettings.

| Configuration         | Value                         | Notes                                                                                      |
| --------------------- | ----------------------------- | ------------------------------------------------------------------------------------------ |
| WebGL Template        | `PROJECT:AITTemplate`         | hard-coded                                                                                 |
| Linker Target         | `Wasm`                        | hard-coded                                                                                 |
| Scripting Backend     | `IL2CPP`                      | hard-coded                                                                                 |
| Memory Size           | 256\~1536MB                   | Default values by Unity version (user override possible)                                   |
| Compression           | `Brotli`                      | Default value. `decompressionFallback`is enabled, so it is available in all Unity versions |
| Threading             | `false`                       | Default value (mobile browser compatibility)                                               |
| Data Caching          | `false`                       | Default                                                                                    |
| `nameFilesAsHashes`   | user setting (default `true`) | forced only in Unity 2021.x `false` — `true`causes a Bee build loop bug                    |
| Engine Code Stripping | user setting                  | —                                                                                          |
| Managed Stripping     | `High`                        | Default                                                                                    |
| IL2CPP Config         | user setting                  | —                                                                                          |

The single source of truth for default values is `AITEditorScriptObject`of `GetDefault*` static methods. Only the Dev Server profile lowers compression `Disabled`to this — profile-specific differences are [Build profile](https://developers-apps-in-toss.toss.im/documentation/unity/build/build-profiles)documented there.

Default memory by version:

* Unity 2021.3: 256MB
* Unity 2022.3: 512MB
* Unity 6 (2023.3+): 1024MB
* Unity 2024.2+: 1536MB

Environment variable overrides applied to profiles are `AITBuildInitializer.ApplyEnvironmentVariableOverrides`handles them. The list of variables and their values [Build profile](https://developers-apps-in-toss.toss.im/documentation/unity/build/build-profiles)is the source of truth.

#### Config validation

`DoExport`on startup `UnityUtil.GetEditorConf()`reads the settings asset, and if the asset itself cannot be found, `INVALID_APP_CONFIG`is returned.

An empty app ID or icon URL does not block the build. The app ID is only a condition that disables the build button in the Configuration window (`AITEditorScriptObject.IsAppNameValid`), and the icon URL is only format-checked when entered. In other words, if you build with empty values as-is, `%AIT_ICON_URL%` and similar values become packages with empty strings substituted.

### Phase 1 WebGL build

`AITConvertCore.BuildWebGL()`

#### execution flow

```
1. AITBuildInitializer.Init(profile)
   ├── Automatically configure PlayerSettings
   ├── Apply environment variable overrides
   └── Output build profile log

2. If cleanBuild:
   └── Delete the webgl/ directory

3. BuildPipeline.BuildPlayer()
   ├── scenes: EditorBuildSettings.scenes (checked only)
   ├── locationPathName: "{projectPath}/webgl"
   ├── target: WebGL
   └── options: BuildOptions.None (add BuildOptions.CleanBuildCache if cleanBuild)

4. Inspect BuildReport
   ├── success → write .ait-build-info.json
   └── failure → AITErrorReporter.SetBuildReport(report) + return error

5. Write build marker: webgl/.ait-build-info.json
```

#### Build marker

After a successful WebGL build `webgl/.ait-build-info.json`records metadata in. The schema is `AITConvertCore.cs`of `AITBuildInfo` class.

```json
{
    "sdkVersion": "1.7.0",
    "buildTime": "2024-03-01T12:00:00.0000000Z",
    "compressionFormat": 2,
    "decompressionFallback": true,
    "profileName": "Production",
    "unityVersion": "6000.2.15f1"
}
```

| Field                   | Description                                                                                               |
| ----------------------- | --------------------------------------------------------------------------------------------------------- |
| `sdkVersion`            | SDK package version                                                                                       |
| `buildTime`             | UTC ISO 8601 build time                                                                                   |
| `compressionFormat`     | `PlayerSettings.WebGL.compressionFormat` int value (0=Disabled, 1=Gzip, 2=Brotli)                         |
| `decompressionFallback` | `PlayerSettings.WebGL.decompressionFallback`. If enabled, the output extension becomes `.unityweb`becomes |
| `profileName`           | "Development" or "Production"                                                                             |
| `unityVersion`          | `Application.unityVersion`                                                                                |

`ReadBuildMarker(webglPath)`reads the marker and returns `AITBuildInfo`, and if the file does not exist or parsing fails, `null`return.

marker use cases:

* **build cache validation** (`ShouldForceCleanBuild`) — auto clean build if Unity version mismatches or there is no marker
* **compression format detection** (`CopyWebGLToPublic`) — `compressionFormat`and `decompressionFallback`accurate extension determined by

#### build cache integrity check

`ShouldForceCleanBuild(outputPath, cleanBuild)`validates the existing cache before the WebGL build.

```
1. cleanBuild=true → always clean build
2. No webgl/ folder → new build (clean not needed)
3. No build marker → clean build (previous SDK version or corruption)
4. Unity version mismatch → clean build (guarantee build output compatibility)
5. Build/*.loader.js missing → clean build (required file missing)
6. All pass → incremental build
```

Unity's `BuildPipeline`performs incremental builds by default, so `webgl/`if it remains, only changed assets are recreated. `cleanBuild=true`if it is `webgl/`deletes `BuildOptions.CleanBuildCache`and performs a full build.

### Phase 2 packaging

`AITPackageBuilder.PackageWebGLBuild()` (sync) or `PackageWebGLBuildAsync()` (async). Both paths share common preparation logic through `PreparePackaging()`to share common preparation logic.

```
PreparePackaging() ← common to sync/async
├── Wait for Node.js/pnpm installation
├── Create ait-build/ directory
├── CopyBuildConfigFromTemplate()
│   ├── Copy SDK BuildConfig~/ → ait-build/
│   └── Copy pnpm-lock.yaml
├── CopyWebGLToPublic()
│   ├── Validate webgl/Build/ (detect files by compression format)
│   ├── Copy Build → ait-build/public/Build/
│   ├── Copy TemplateData → ait-build/public/TemplateData/
│   ├── Copy Runtime → ait-build/public/Runtime/
│   ├── Replace index.html placeholders → ait-build/index.html
│   ├── Insert loading screen
│   └── Validate placeholders
├── Check pnpm path
└── ValidateNodeModulesIntegrity()

Sync path: RunPnpmInstallSync() → RunGraniteBuildSync()
Async path: RunPnpmInstallAsync() → RunGraniteBuildAsync()

Output: ait-build/dist/
```

#### Copy BuildConfig

`CopyBuildConfigFromTemplate`of this SDK `WebGLTemplates/AITTemplate/BuildConfig~/`the `ait-build/`copies to.

| File                     | processing                                                         |
| ------------------------ | ------------------------------------------------------------------ |
| `package.json`           | merge dependencies                                                 |
| `tsconfig.json`          | merge compilerOptions                                              |
| `vite.config.ts`         | `%AIT_VITE_HOST%`, `%AIT_VITE_PORT%` replace                       |
| `granite.config.ts`      | replace 13 placeholders                                            |
| `apps-in-toss.config.ts` | 3.x configuration file. `granite.config.ts`placeholder set such as |
| `pnpm-lock.yaml`         | copy if present                                                    |

The actual merge rules are in `Package/BuildConfigMerger.cs`here.

#### Copy WebGL to public

`CopyWebGLToPublic`If it is `webgl/` rearranges the output into a `ait-build/` structure.

```
webgl/
├── Build/
│   ├── webgl.loader.js          → ait-build/public/Build/
│   ├── webgl.data               → ait-build/public/Build/
│   ├── webgl.framework.js       → ait-build/public/Build/
│   └── webgl.wasm               → ait-build/public/Build/
├── TemplateData/                → ait-build/public/TemplateData/
├── Runtime/                     → ait-build/public/Runtime/
└── index.html                   → ait-build/index.html (after replacement)
```

During this process, placeholder replacement and loading screen insertion happen together. The replacement rules are in the section below **Placeholder replacement** section. The behavior and customization of the loading screen itself are [Loading screen customization](https://developers-apps-in-toss.toss.im/documentation/unity/build/loading-screen-customization)is the source of truth.

#### pnpm install

**Determines whether installation can be skipped.** To avoid rerunning install on every build, immediately after a successful install `Package/PnpmInstallStateMarker.cs`is `package.json`·`pnpm-lock.yaml`stores the content hash and pnpm version of `ait-build/node_modules/.ait-install-state.json`in. On the next build, if all these values match and `node_modules` the integrity check passes, install is skipped.

The reason the marker is `node_modules` **placed inside** is that `NodeModulesValidator.CleanNodeModules`is `node_modules`if it is deleted entirely, the marker is invalidated as well — automatically aligning with the clean stage of the retry policy. Any case where determination is impossible, such as when there is no marker or parsing fails, is handled fail-closed as "cannot skip." This is because the cost of an incorrect skip (build failure) is greater than the cost of an unnecessary reinstall (wasted time).

The kill switch is the environment variable `AIT_DISABLE_INSTALL_SKIP`. `1`/`true`If set, skipping is disabled, and if the value cannot be interpreted, a warning is logged and skipping is disabled as well — it behaves fail-safe so a typo cannot neutralize the kill switch.

**3-stage retry.** If not skipped `PnpmInstallStages` proceeds in the order defined in the array.

```
┌──────────────────────────────────────┐
│  ValidateNodeModulesIntegrity()      │
│  web-framework version mismatch?      │
│  → Delete node_modules and reinstall  │
└─────────────┬────────────────────────┘
              │
              ▼
┌──────────────────────────────────────┐
│  1st: pnpm install --frozen-lockfile │  ← fastest (no lockfile changes)
│  Successful? → done                  │
│  Failed? ↓                           │
├──────────────────────────────────────┤
│  2nd: pnpm install                   │  ← lockfile updates allowed
│       --no-frozen-lockfile           │
│  Successful? → done                  │
│  Failed? ↓                           │
├──────────────────────────────────────┤
│  3rd: CleanNodeModules()             │  ← delete node_modules + .npm-cache
│       + pnpm install                 │
│         --no-frozen-lockfile         │
│  Successful? → done                  │
│  Failed? → FAIL_NPM_BUILD error      │
└──────────────────────────────────────┘
```

`ValidateNodeModulesIntegrity()`validation order:

1. `node_modules/`valid if absent (newly installed)
2. `node_modules/.pnpm/` invalid if the directory does not exist (stale modules)
3. `package.json`in `@apps-in-toss/web-framework` version extraction
4. `node_modules/.pnpm/@apps-in-toss+web-framework@{version}*/` existence check — if version mismatch or package is missing, warn and mark invalid

#### granite build

```bash
pnpm run build   # → run granite build
```

if it fails `CleanNodeModules()` → `pnpm install --no-frozen-lockfile` → `pnpm run build`retry once, and if it still fails, `FAIL_NPM_BUILD`is returned.

The output is `ait-build/dist/`and if the file is missing here, `.ait` file missing `DIST_FOLDER_MISSING` or `AIT_FILE_MISSING`leads to.

### File signature detection and validation

`AITBuildValidator`verifies the existence and integrity of the WebGL output.

#### Search patterns by compression format

`GetFilePatterns(compressionFormat, decompressionFallback)`Determines the search patterns based on this build marker value.

| Condition                      | data pattern      | framework pattern         | wasm pattern      |
| ------------------------------ | ----------------- | ------------------------- | ----------------- |
| `decompressionFallback = true` | `*.data.unityweb` | `*.framework.js.unityweb` | `*.wasm.unityweb` |
| `0` Disabled                   | `*.data`          | `*.framework.js`          | `*.wasm`          |
| `1` Gzip                       | `*.data.gz`       | `*.framework.js.gz`       | `*.wasm.gz`       |
| `2` Brotli                     | `*.data.br`       | `*.framework.js.br`       | `*.wasm.br`       |
| Other (fallback)               | `*.data*`         | `*.framework.js*`         | `*.wasm*`         |

`decompressionFallback`If this is enabled, it takes precedence over the compression format. Since the loader is not a compression target, it is always `*.loader.js`.

#### File detection

`FindFileInBuild(buildPath, pattern, isRequired)`Finds files with a glob pattern. `*.data*` The same trailing wildcard also `*.data.meta`matches, so `.meta`is excluded from the results — if it is not excluded, `LastWriteTime` in sorting, `.meta`is selected as the newest and the wrong file name is returned.

| Pattern           | Required | Description        |
| ----------------- | -------- | ------------------ |
| `*.loader.js`     | Yes      | Unity WebGL loader |
| `*.data*`         | Yes      | Game data          |
| `*.framework.js*` | Yes      | Unity framework    |
| `*.wasm*`         | Yes      | WebAssembly binary |
| `*.symbols.json*` | No       | debug symbols      |

**Auto-clean on duplicate matches.** If multiple files match one pattern, `LastWriteTime` they are sorted in descending order (and by file name descending on ties), leaving only the newest one and deleting the rest `.meta`with it. The approach of only leaving warning logs was changed to deletion because it happened repeatedly on every build and accumulated Sentry noise. If any file fails to be deleted, an informational log recommending a Clean Build is written.

**Required file missing.** `isRequired=true`If a pattern cannot be found, only the first line is sent to Sentry (so fingerprints are stably grouped by pattern), and the remaining diagnostic lines are left only in the console. Diagnostics include the search path, the following strings, and the actual file list in the Build folder (or that it is empty).

```
If this file is missing, a 'createUnityInstance is not defined' error occurs at runtime.
```

The return value is an empty string, and the caller `REQUIRED_FILE_MISSING`leads to.

#### Placeholder substitution validation

`ValidatePlaceholderSubstitution(content, filePath)`This regex `%[A-Z_]+%`finds unsubstituted placeholders.

Critical (error + build failure):

* `%UNITY_WEBGL_LOADER_URL%`
* `%UNITY_WEBGL_DATA_URL%`
* `%UNITY_WEBGL_FRAMEWORK_URL%`
* `%UNITY_WEBGL_CODE_URL%`

Others `%...%` patterns are only warnings. The following empty path patterns are also treated as critical:

```html
src="Build/"     ← means loader.js is missing
"Build/"         ← means the data file is missing
Build/",         ← empty file name after the separator
```

`apps-in-toss.config.ts`In this case, unsubstituted placeholders in the SDK\_GENERATED area are hard errors, but SDK placeholders left in the USER\_CONFIG area or keys moved in 3.x are only warnings — when merged, SDK values take precedence, so the build result is normal.

#### Build completion report

`PrintBuildReport(buildProjectPath, distPath)`is `ait-build/public/Build/`scans and prints whether 4 required patterns and 1 optional pattern exist, along with file sizes. If a required pattern is missing, `Debug.LogError`as a `[Missing!]`is displayed, and optional patterns are displayed only when present.

### Placeholder replacement

#### index.html

`AITPackageBuilder.CopyWebGLToPublic()`is performed in.

**Unity standard**

| Placeholder               | Source                                  |
| ------------------------- | --------------------------------------- |
| `%UNITY_WEB_NAME%`        | `PlayerSettings.productName`            |
| `%UNITY_WIDTH%`           | `PlayerSettings.defaultWebScreenWidth`  |
| `%UNITY_HEIGHT%`          | `PlayerSettings.defaultWebScreenHeight` |
| `%UNITY_COMPANY_NAME%`    | `PlayerSettings.companyName`            |
| `%UNITY_PRODUCT_NAME%`    | `PlayerSettings.productName`            |
| `%UNITY_PRODUCT_VERSION%` | `PlayerSettings.bundleVersion`          |

**Unity WebGL URL** — build will fail if not substituted.

| Placeholder                   | Substitution value                      |
| ----------------------------- | --------------------------------------- |
| `%UNITY_WEBGL_LOADER_URL%`    | `Build/{loaderFile}`                    |
| `%UNITY_WEBGL_DATA_URL%`      | `Build/{dataFile}`                      |
| `%UNITY_WEBGL_FRAMEWORK_URL%` | `Build/{frameworkFile}`                 |
| `%UNITY_WEBGL_CODE_URL%`      | `Build/{wasmFile}`                      |
| `%UNITY_WEBGL_SYMBOLS_URL%`   | `Build/{symbolsFile}` (or empty string) |

**Legacy file names** — for backward compatibility. Only the file name is substituted, without the path.

| Placeholder                        | Substitution value |
| ---------------------------------- | ------------------ |
| `%UNITY_WEBGL_LOADER_FILENAME%`    | `{loaderFile}`     |
| `%UNITY_WEBGL_DATA_FILENAME%`      | `{dataFile}`       |
| `%UNITY_WEBGL_FRAMEWORK_FILENAME%` | `{frameworkFile}`  |
| `%UNITY_WEBGL_CODE_FILENAME%`      | `{wasmFile}`       |
| `%UNITY_WEBGL_SYMBOLS_FILENAME%`   | `{symbolsFile}`    |

**AIT custom**

| Placeholder                  | Substitution value   | Description                                                                                                                                                            |
| ---------------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `%AIT_ENABLE_DEBUG_CONSOLE%` | `"true"` / `"false"` | Enable debug console                                                                                                                                                   |
| `%AIT_DEVICE_PIXEL_RATIO%`   | Number               | Device pixel ratio                                                                                                                                                     |
| `%AIT_ICON_URL%`             | URL string           | App icon URL                                                                                                                                                           |
| `%AIT_DISPLAY_NAME%`         | String               | App display name                                                                                                                                                       |
| `%AIT_PRIMARY_COLOR%`        | Color code           | Brand color (default: `#3182f6`)                                                                                                                                       |
| `%AIT_PRELOAD_TAGS%`         | HTML tag             | `<link rel="preload">` Tag                                                                                                                                             |
| `%AIT_LOADING_SCREEN%`       | HTML string          | The entire loading screen content. [Loading screen customization](https://developers-apps-in-toss.toss.im/documentation/unity/build/loading-screen-customization) Note |

#### Preload tags

`GeneratePreloadTags(dataFile, wasmFile, frameworkFile)`is generated.

```html
<link rel="preload" href="Build/webgl.data" as="fetch">
<link rel="preload" href="Build/webgl.wasm" as="fetch">
```

> **Important**: framework.js is not preloaded. If the Unity loader loads framework.js `<script>` with a tag, `as="fetch"` the preload and cache keys may not match, causing duplicate downloads. This increases memory pressure and raises the probability of intermittent initialization failures (ASM\_CONSTS errors).

#### granite.config.ts

`Package.BuildConfigMerger.UpdateGraniteConfig()`replaces 13 items.

| Placeholder                                 | Source                                   |
| ------------------------------------------- | ---------------------------------------- |
| `%AIT_APP_NAME%`                            | `config.appName`                         |
| `%AIT_DISPLAY_NAME%`                        | `config.displayName`                     |
| `%AIT_PRIMARY_COLOR%`                       | `config.primaryColor`                    |
| `%AIT_ICON_URL%`                            | `config.iconUrl`                         |
| `%AIT_BRIDGE_COLOR_MODE%`                   | `config.GetBridgeColorModeString()`      |
| `%AIT_WEBVIEW_TYPE%`                        | `config.GetWebViewTypeString()`          |
| `%AIT_NAVIGATION_BAR%`                      | `config.GetNavigationBarJson()`          |
| `%AIT_ALLOWS_INLINE_MEDIA_PLAYBACK%`        | `config.allowsInlineMediaPlayback`       |
| `%AIT_MEDIA_PLAYBACK_REQUIRES_USER_ACTION%` | `config.mediaPlaybackRequiresUserAction` |
| `%AIT_VITE_HOST%`                           | `config.viteHost`                        |
| `%AIT_VITE_PORT%`                           | `config.vitePort`                        |
| `%AIT_PERMISSIONS%`                         | `config.GetPermissionsJson()`            |
| `%AIT_OUTDIR%`                              | `config.outdir`                          |

#### vite.config.ts

`%AIT_VITE_HOST%` → `config.viteHost`, `%AIT_VITE_PORT%` → `config.vitePort`.

### template merge timing

`AITTemplateManager`merges the SDK template and project custom area based on markers. The marker syntax and the editable areas are [Build customization](https://developers-apps-in-toss.toss.im/documentation/unity/build/build-customization)the source of truth. Here, the merge is **when and what** happens only.

The merge occurs in Phase 0 `EnsureWebGLTemplatesExist`at, that is, before the Unity WebGL build starts.

```
When the SDK is updated:
  ├── index.html:
  │   ├── No markers (older version) → replace with SDK template + warning
  │   └── Markers present → preserve USER_HEAD and USER_BODY_END areas, update the rest
  ├── vite.config.ts, granite.config.ts, apps-in-toss.config.ts:
  │   └── Preserve USER_CONFIG area, update SDK_GENERATED area
  ├── Runtime/ → always overwrite with the SDK version (debug console, etc.)
  └── TemplateData/ → always overwrite with the SDK version
```

If an older version of index.html without markers is found, the following warning is issued and it is replaced with the SDK template.

```
[AIT] Template updated: replacing the old version template with the new marker-based template.
⚠️ If the existing index.html had custom modifications, reapply them manually to the USER_* marker areas.
```

### Node.js and pnpm management

The SDK downloads and uses its own Node.js regardless of the system installation. The single source of truth for versions is `AITNodeJSDownloader.cs`of `NODE_VERSION`and `AITPackageManagerHelper.cs`of `PNPM_VERSION`.

`PNPM_VERSION`is `package.json`, `sdk-runtime-generator~/package.json`, `WebGLTemplates/AITTemplate/BuildConfig~/package.json` The `packageManager` field in the three places must always be the same. If the values diverge, the pnpm used by the client and the pnpm that updated the lockfile differ, causing specifier drift.

The installation path is `~/.ait-unity-sdk/nodejs/v{NODE_VERSION}/{platform}/`.

Download mirrors fall back in order.

1. `https://nodejs.org/dist/` (official)
2. `https://cdn.npmmirror.com/binaries/node/`
3. `https://repo.huaweicloud.com/nodejs/`

```
1. Check installation path → skip if already present
2. Try mirror 1:
   ├── Download .tar.gz (macOS/Linux) or .zip (Windows)
   ├── Verify SHA256 checksum ← if it fails, delete the downloaded file + try next mirror
   └── Extract → temporary folder
3. Fallback to mirror 2/3 (same process)
4. Atomically move temporary folder to final path
5. Install pnpm: corepack enable + corepack prepare
```

Platform-specific SHA256 hashes are `AITNodeJSDownloader.cs`hardcoded in. Node.js and pnpm executable path resolution and process management are handled by `AITPackageManagerHelper`is responsible for this.

### Error code

`AITConvertCore.AITExportError` is an enum. The value `7`was previously `WEBGL_BUILD_INCOMPLETE`but `10`\~`13`was removed when it was refined into more detailed values.

| Code                              | Value | Short label                 |
| --------------------------------- | ----- | --------------------------- |
| `SUCCEED`                         | 0     | Success                     |
| `NODE_NOT_FOUND`                  | 1     | No Node.js                  |
| `BUILD_WEBGL_FAILED`              | 2     | WebGL build error           |
| `INVALID_APP_CONFIG`              | 3     | App settings error          |
| `NETWORK_ERROR`                   | 4     | Network error               |
| `CANCELLED`                       | 5     | User canceled               |
| `FAIL_NPM_BUILD`                  | 6     | pnpm build error            |
| `BUILD_FOLDER_MISSING`            | 10    | No Build folder             |
| `REQUIRED_FILE_MISSING`           | 11    | Required file missing       |
| `INDEX_HTML_MISSING`              | 12    | No index.html               |
| `PLACEHOLDER_SUBSTITUTION_FAILED` | 13    | Placeholder not substituted |
| `DIST_FOLDER_MISSING`             | 14    | No dist folder              |
| `AIT_FILE_MISSING`                | 15    | No .ait file                |

The full message shown to the user and the short label are both owned by `AITExportErrorCatalog`owns them.

```
Build error occurred
  ↓
ShowComplexDialog("Build failed", errorMessage, ...)
  ├── "OK" → exit
  └── "Report Issue" → AITErrorReporter.OpenIssueInBrowser()
                     → opens a prefilled issue URL in GitHub Issues
```

### User warnings and dialog conditions

#### Error dialog

| Condition                | Title           | Contents                                       |
| ------------------------ | --------------- | ---------------------------------------------- |
| No SDK loading template  | "Error"         | Could not find the SDK loading screen template |
| No Build folder          | "Error"         | Could not find the WebGL build folder          |
| App name not set         | "Error"         | The app name is not set                        |
| Deployment key not set   | "Error"         | The deployment key is not set                  |
| pnpm installation failed | "Build failed"  | Failed to install pnpm                         |
| Build canceled           | "Canceled"      | The build was canceled                         |
| Build succeeded          | "Success"       | Build and packaging completed                  |
| Clean completed          | "Done"          | Clean completed successfully                   |
| Deployment timeout       | "Timeout"       | Deployment timed out                           |
| Port conflict            | "Port conflict" | That port is already in use                    |

#### Confirmation dialog

| Condition               | Behavior                                                                  |
| ----------------------- | ------------------------------------------------------------------------- |
| `AIT/Clean`             | "Would you like to delete the webgl/ and ait-build/ folders?"             |
| Deployment confirmation | "App name: X, version: Y — Deploy?" (memo auto-generated value displayed) |
| Reset settings          | "Would you like to reset the settings?"                                   |
| Reset loading screen    | "Would you like to reset the loading screen to the default template?"     |

#### 3-way dialog

| Condition         | Option                |
| ----------------- | --------------------- |
| Build failed      | "OK" / "Report Issue" |
| Deployment failed | "OK" / "Report Issue" |

#### Console warning

| Condition                                        | Message summary                                     |
| ------------------------------------------------ | --------------------------------------------------- |
| Non-critical placeholder not substituted         | Display the placeholder name                        |
| SDK-managed settings remain in USER\_CONFIG      | Deletion recommended (build is normal)              |
| `pnpm install --frozen-lockfile` Failure         | Proceed to the next retry step                      |
| web-framework version mismatch                   | Expected vs actual version display                  |
| `node_modules/.pnpm` None                        | stale modules                                       |
| Upgrading previous version templates             | Instructions for manually reapplying custom changes |
| No loading screen file                           | An empty loading screen is used                     |
| Failed to write build marker                     | Warnings only (build continues)                     |
| No build marker / Unity version mismatch         | Automatic clean build                               |
| `AIT_DISABLE_INSTALL_SKIP` Unable to parse value | Treated as skip disabled                            |

#### Console error

| Condition                      | Result                            |
| ------------------------------ | --------------------------------- |
| `webgl/Build/` No folder       | `BUILD_FOLDER_MISSING`            |
| Required WebGL file missing    | `REQUIRED_FILE_MISSING`           |
| `index.html` None              | `INDEX_HTML_MISSING`              |
| Fatal placeholder not replaced | `PLACEHOLDER_SUBSTITUTION_FAILED` |
| Empty path pattern detected    | `PLACEHOLDER_SUBSTITUTION_FAILED` |
| No dist after granite build    | `DIST_FOLDER_MISSING`             |
| In dist `.ait` None            | `AIT_FILE_MISSING`                |
| Final pnpm install failure     | Build aborted                     |
| No SDK BuildConfig folder      | Build aborted                     |
| No SDK WebGLTemplates folder   | Build aborted                     |

### Server lifecycle

The local server is only the Dev Server (the Production Server that used to exist was removed starting from 3.0.0 because it became impossible to connect sandbox apps — to verify production settings on a real device, [Deploy (Test) in Getting Started](https://developers-apps-in-toss.toss.im/documentation/unity/first-steps/getting-started)use it).

| Method                      | Description                                                           |
| --------------------------- | --------------------------------------------------------------------- |
| `StartServer()`             | Build + start server                                                  |
| `StopServer()`              | Terminate server process                                              |
| `RestartServer(serverOnly)` | `serverOnly=false`then build + server, `true`then restart server only |

```
AIT/Dev Server/
├── Start Server              → StartServer() → DoExport(dev) + granite dev
├── Stop Server               → StopServer()
├── Restart Server            → RestartServer(serverOnly: false)
└── Restart Server (server-only) → RestartServer(serverOnly: true)
```

If the target port is already in use, show a "port conflict" dialog, and the user must change the port or terminate the occupying process.

### Error reporting

`AITErrorReporter`is `[InitializeOnLoad]`When the editor starts, with `Application.logMessageReceived`subscribes to and captures all console logs in a circular buffer.

| Buffer        | Maximum size | Capture targets                      |
| ------------- | ------------ | ------------------------------------ |
| `errorLogs`   | 50           | `LogType.Error`, `LogType.Exception` |
| `warningLogs` | 30           | `LogType.Warning`                    |
| `infoLogs`    | 20           | `LogType.Log`, `LogType.Assert`      |

`OpenIssueInBrowser(errorCode, profileName)`automatically constructs the GitHub Issue URL from this buffer. The title is `[Build Error] {errorCode}`and the body includes the SDK/Unity/OS versions, profile name, error code and message, app settings, `BuildReport` errors (if any), and the most recent console logs. If the URL exceeds 2000 characters, `infoLogs` → `warningLogs` → `errorLogs` it is trimmed step by step in that order.

The scope of logs sent to Sentry and the noise-suppression policy are [Sentry integration](https://developers-apps-in-toss.toss.im/documentation/unity/add-features/sentry-integration)here.

### Related documents

* [Build profile](https://developers-apps-in-toss.toss.im/documentation/unity/build/build-profiles) — per-profile setting differences, environment variable overrides
* [Build customization](https://developers-apps-in-toss.toss.im/documentation/unity/build/build-customization) — marker area contract, web entry point editing
* [Loading screen customization](https://developers-apps-in-toss.toss.im/documentation/unity/build/loading-screen-customization) — loading screen replacement and `AITLoading` API
* [Sentry integration](https://developers-apps-in-toss.toss.im/documentation/unity/add-features/sentry-integration) — error collection and context injection
* [Troubleshooting](https://developers-apps-in-toss.toss.im/documentation/unity/first-steps/faq) — when the build gets stuck


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://developers-apps-in-toss.toss.im/documentation/api-and-sdk-en/unity/build/build-process.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
