For the complete documentation index, see llms.txt. This page is also available as Markdown.

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 profileand Build customizationthis 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 DoExportrestarts only the granite process without calling it.

Phase 0 initialization

Template synchronization

AITTemplateManager.EnsureWebGLTemplatesExistcopies 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.Initautomatically 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. decompressionFallbackis 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 falsetruecauses 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 AITEditorScriptObjectof GetDefault* static methods. Only the Dev Server profile lowers compression Disabledto this — profile-specific differences are Build profiledocumented 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.ApplyEnvironmentVariableOverrideshandles them. The list of variables and their values Build profileis the source of truth.

Config validation

DoExporton startup UnityUtil.GetEditorConf()reads the settings asset, and if the asset itself cannot be found, INVALID_APP_CONFIGis 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

Build marker

After a successful WebGL build webgl/.ait-build-info.jsonrecords metadata in. The schema is AITConvertCore.csof AITBuildInfo class.

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 .unitywebbecomes

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, nullreturn.

marker use cases:

  • build cache validation (ShouldForceCleanBuild) — auto clean build if Unity version mismatches or there is no marker

  • compression format detection (CopyWebGLToPublic) — compressionFormatand decompressionFallbackaccurate extension determined by

build cache integrity check

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

Unity's BuildPipelineperforms incremental builds by default, so webgl/if it remains, only changed assets are recreated. cleanBuild=trueif it is webgl/deletes BuildOptions.CleanBuildCacheand 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.

Copy BuildConfig

CopyBuildConfigFromTemplateof 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.tsplaceholder set such as

pnpm-lock.yaml

copy if present

The actual merge rules are in Package/BuildConfigMerger.cshere.

Copy WebGL to public

CopyWebGLToPublicIf it is webgl/ rearranges the output into a ait-build/ structure.

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 customizationis 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.csis package.json·pnpm-lock.yamlstores the content hash and pnpm version of ait-build/node_modules/.ait-install-state.jsonin. 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.CleanNodeModulesis node_modulesif 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/trueIf 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()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.jsonin @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

if it fails CleanNodeModules()pnpm install --no-frozen-lockfilepnpm run buildretry once, and if it still fails, FAIL_NPM_BUILDis returned.

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

File signature detection and validation

AITBuildValidatorverifies 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*

decompressionFallbackIf 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.metamatches, so .metais excluded from the results — if it is not excluded, LastWriteTime in sorting, .metais 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 .metawith 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=trueIf 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).

The return value is an empty string, and the caller REQUIRED_FILE_MISSINGleads 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:

apps-in-toss.config.tsIn 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.LogErroras 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 Note

Preload tags

GeneratePreloadTags(dataFile, wasmFile, frameworkFile)is generated.

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

AITTemplateManagermerges the SDK template and project custom area based on markers. The marker syntax and the editable areas are Build customizationthe source of truth. Here, the merge is when and what happens only.

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

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

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.csof NODE_VERSIONand AITPackageManagerHelper.csof PNPM_VERSION.

PNPM_VERSIONis 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/

Platform-specific SHA256 hashes are AITNodeJSDownloader.cshardcoded in. Node.js and pnpm executable path resolution and process management are handled by AITPackageManagerHelperis responsible for this.

Error code

AITConvertCore.AITExportError is an enum. The value 7was previously WEBGL_BUILD_INCOMPLETEbut 10~13was 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 AITExportErrorCatalogowns them.

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 Starteduse it).

Method
Description

StartServer()

Build + start server

StopServer()

Terminate server process

RestartServer(serverOnly)

serverOnly=falsethen build + server, truethen restart server only

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

AITErrorReporteris [InitializeOnLoad]When the editor starts, with Application.logMessageReceivedsubscribes 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, infoLogswarningLogserrorLogs it is trimmed step by step in that order.

The scope of logs sent to Sentry and the noise-suppression policy are Sentry integrationhere.

Was this helpful?