전체 글 목록으로

Cluewren 자동 플레이로 Unity 성능 회귀 측정하기

빌드마다 수동으로 같은 경로를 플레이하며 성능을 측정하는 작업은 시간이 오래 걸리며, 빌드 간의 정확한 비교도 어렵게 만듭니다. 오픈 소스(Apache-2.0)인 Cluewren을 사용하면 개발자가 사용하는 코딩 에이전트(Claude Code 또는 Codex)가 Cluewren의 MCP 서버를 통해 Unity, Godot, Unreal Engine 에디터에 열려 있는 게임을 직접 플레이합니다. 에이전트가 코드를 읽고 C# 하네스를 작성하면, TypeSafe의 빠른 의사결정 모델인 Jev가 엔진 플러그인을 통해 플레이를 수행하고, 에이전트가 실측 근거와 함께 판정 결과를 보고합니다. 이 플레이 도중 Framedash Unity SDK로 프레임 타임을 기록합니다. 이 글에서는 두 빌드에서 동일한 경로를 주행하며 어느 빌드의, 레벨 내 어느 구역이 느려졌는지 특정합니다. 이번 예시에서 후보 빌드의 성능 저하는 무거운 에셋이나 이펙트를 대신해 시뮬레이션으로 발생시켰습니다.

사용한 환경과 전제 조건

이번 검증에는 다음 환경을 사용했습니다.

  • Unity: Unity 6000.6.7f1
  • 샘플 프로젝트: Unity의 FPS Microgame(MainScene)에 Cluewren Unity 플러그인을 설치한 프로젝트 사본
  • Cluewren 동작 요건: Windows 11(테스트 완료), .NET 10 SDK, 소스에서 빌드, TypeSafe API 키, Claude Code 또는 Codex

Cluewren 자체는 Cluewren 서버로 데이터를 전송하지 않습니다. 게임플레이 도중 의사결정 요청은 TypeSafe의 API로 전송되며, 에이전트는 각자의 제공업체와 통신합니다.

플러그인과 SDK 설치

Cluewren 설치 방법은 Cluewren 설치 가이드를, Unity 프로젝트에 플러그인을 추가하는 방법은 Cluewren Unity 플러그인 문서를 참조하세요. Framedash Unity SDK v0.1.8은 Unity의 Package Manager git URL을 통해 추가합니다.

https://github.com/crane-valley/framedash-unity-sdk.git#v0.1.8

측정 프로브 스크립트 추가

프로젝트에 다음 스크립트를 추가합니다.

using System;
using Framedash;
using Unity.FPS.Gameplay;
using UnityEngine;

public sealed class FramedashPlaytestProbe : MonoBehaviour
{
    const string MapId = "fps-microgame-main";
    const float Interval = 0.25f;

    PlayerCharacterController _player;
    float _next;

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
    static void Boot()
    {
        if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("FRAMEDASH_API_KEY")))
            return;
        TelemetrySDK.Initialize(buildId: Environment.GetEnvironmentVariable("FRAMEDASH_BUILD_ID"), enableOfflineQueue: false);
        TelemetrySDK.Instance.BeginAutomatedSessionFromEnvironment();
        var go = new GameObject(nameof(FramedashPlaytestProbe));
        DontDestroyOnLoad(go);
        go.AddComponent<FramedashPlaytestProbe>();
    }

    void Update()
    {
        if (Time.unscaledTime < _next)
            return;
        _next = Time.unscaledTime + Interval;
        if (_player == null)
            _player = FindAnyObjectByType<PlayerCharacterController>();
        if (_player != null)
            TelemetrySDK.Instance.Track("player_position", MapId, _player.transform.position);
    }

    void OnApplicationQuit() => TelemetrySDK.Instance.FlushBlocking(5000);
}

이 스크립트는 FRAMEDASH_API_KEY가 설정된 경우에만 SDK를 초기화합니다. FRAMEDASH_BUILD_ID와 FRAMEDASH_TEST_SCENARIO에서 빌드 태그가 자동으로 적용됩니다. 플레이어의 위치는 0.25초마다 map_id가 fps-microgame-main으로 지정된 player_position 이벤트로 전송됩니다. SDK 동작에 따라 모든 Track() 이벤트에는 해당 시점의 프레임 타임, FPS, GPU 시간, 메모리가 함께 기록되므로 위치 표본은 구역별 프레임 타임 표본 역할을 겸합니다. 또한 SDK는 10초마다 perf_heartbeat 이벤트를 자동으로 전송합니다.

환경 변수 설정과 에디터 실행

Unity 에디터를 실행하기 전에 다음 환경 변수를 설정합니다.

FRAMEDASH_API_KEY=<events:write 권한의 Ingest 키>
FRAMEDASH_BUILD_ID=fps-walk-base
FRAMEDASH_TEST_SCENARIO=cluewren-walkthrough

에이전트가 하네스를 작성하고 수정하는 첫 번째 실행에는 비교 대상에서 이벤트를 제외하기 위해 별도의 빌드 ID(이번에는 fps-walkthrough-baseline 사용)를 지정합니다. 이후 측정에서는 변수를 설정한 새 에디터를 각각 실행하여, 측정 대상 베이스라인 실행에는 fps-walk-base, 후보 빌드 실행에는 fps-walk-heavy-chamber를 지정합니다. API 키는 에디터를 실행하는 환경에만 설정하고, 에이전트 대화창에는 절대 붙여넣지 마세요.

에이전트 지시와 콜드 실행

코딩 에이전트(Claude Code, Opus)에 두 빌드 모두 동일한 지시를 전달합니다.

Walk the player through the whole level: start at the spawn point, go through every area the level contains, and finish back at the start. Do not fight unless an enemy blocks the way.

(플레이어를 조작해 레벨 전체를 순회하세요. 스폰 지점에서 시작해 레벨의 모든 구역을 통과한 뒤 시작 지점으로 돌아옵니다. 적이 길을 막지 않는 한 전투는 치르지 않습니다.)

첫 번째(콜드) 실행에서 에이전트는 3개의 에피소드와 471초가 걸렸습니다. 에피소드 1에서는 필드 가장자리에서 용암으로 떨어졌고, 에피소드 2에서는 HoverBot에 쓰러졌습니다. 에이전트는 매번 하네스를 수정해 에피소드 3에서 순회를 완료했습니다. 이 실행은 별도의 빌드 ID인 fps-walkthrough-baseline으로 기록되었으므로 해당 이벤트는 비교에 포함되지 않습니다. 생성된 하네스는 프로젝트 실행 메모리에 유지되어 동일한 지시에 재사용됩니다.

두 빌드의 실행 결과

후보 빌드에는 하층 챔버(lower chamber) 내부의 부하를 시뮬레이션하는 처리를 추가했습니다. 플레이어가 챔버 안에 머무는 동안 메인 스레드가 매 프레임 12 ms씩 스핀합니다. 이는 실제 무거운 에셋이나 이펙트의 부하를 대신하는 처리입니다.

유지된 웜 하네스를 재사용했으므로 비교 대상이 된 2회의 실행은 모두 1개 에피소드로 끝났습니다.

  • 베이스라인(fps-walk-base): 성공(achieved), 보고 시간 84.7초, 시작 62.0초 만에 스폰 지점으로 복귀. 순회 경로는 중앙 홀, 서쪽 제단, 넓은 필드의 4개 구역, 필드 게이트웨이, 큰 문을 통과하는 계단, 하층 챔버 입구·안쪽 끝·동쪽, 스폰 지점 복귀. 전투는 1회(자신을 공격하던 HoverBot 사살, 소요 시간 2.8초).
  • 후보 빌드(fps-walk-heavy-chamber): 성공(achieved), 보고 시간 83.4초, 계획된 12단계를 동일한 순서로 수행, 시작 62.4초 만에 스폰 지점으로 복귀. 동일한 1회의 전투(2.8초) 수행.

perf-diff와 Regression 페이지로 비교하기

두 빌드의 차이는 analytics:read 권한이 있는 키를 사용해 Framedash CLI(@framedash/cli 0.2.0)의 perf-diff 명령으로 비교합니다.

npx @framedash/cli perf-diff --baseline fps-walk-base --candidate fps-walk-heavy-chamber --metric frame_time --threshold 10 --days 7

각 지표의 출력 결과는 다음과 같습니다.

  • frame_time: 베이스라인 P50 8.92 ms, 후보 빌드 P50 15.09 ms(+69.3 %, isRegression은 true). P95는 52.66 ms 대 53.20 ms.
  • memory: -0.4 %(성능 회귀 없음).
  • gpu_time: -15.6 %(성능 회귀 없음. 값이 1.4~1.6 ms 안팎으로 작아 잡음에 해당).

출력의 isRegression 플래그는 방향성을 나타내며, 지표가 나빠지면 항상 true가 됩니다. 여기에는 백분율 임계값이 적용되지 않습니다. --threshold 값은 --fail-on-regression을 지정했을 때 종료 코드를 제어하는 데에만 쓰입니다. CI에서 임계값을 넘겼을 때 작업을 실패 처리하려면 --fail-on-regression을 추가합니다. CI 구성과 절차는 병합 전에 CI에서 게임 성능 비교하기, CI에서 Unity 성능 회귀 감지하기, 그리고 CLI 레퍼런스를 참조하세요.

대시보드의 Regression 페이지에서도 같은 결과를 확인할 수 있습니다.

Framedash 대시보드의 Regression 페이지에서 베이스라인과 후보 빌드의 프레임 타임을 비교한 화면
대시보드의 Regression 페이지. fps-walk-base와 fps-walk-heavy-chamber의 프레임 타임을 비교하고 있습니다.

대시보드에 표시된 수치도 CLI와 마찬가지로 Frame Time이 +69.3 %(8.92 ms에서 15.09 ms)이며, 표본 수는 두 빌드 모두 299개입니다.

쿼리로 구역별 내역 확인하기

성능 저하가 일어난 위치를 파악하기 위해 CLI의 query 명령으로 SQL을 실행해 구역별로 집계합니다. 이 명령에는 data:admin 권한이 있는 Full 키가 필요합니다(읽기 전용 키는 403 오류를 반환합니다). 하층 챔버는 Unity 월드 좌표 기준으로 바닥면 아래(y < -3)이며 x > 20인 구역입니다.

npx @framedash/cli query "SELECT build_id, (position_y < -3 AND position_x > 20) AS lower_chamber, count() c, round(quantile(0.5)(frame_time_ms),1) p50, round(quantile(0.95)(frame_time_ms),1) p95 FROM events WHERE build_id IN ('fps-walk-base','fps-walk-heavy-chamber') AND event_name='player_position' GROUP BY build_id, lower_chamber ORDER BY build_id, lower_chamber"

집계 결과는 다음과 같습니다.

build_id구역표본 수P50P95
fps-walk-base하층 챔버 외부2277.6 ms52.9 ms
fps-walk-base하층 챔버6511.5 ms36.7 ms
fps-walk-heavy-chamber하층 챔버 외부2247.0 ms53.2 ms
fps-walk-heavy-chamber하층 챔버6826.0 ms54.0 ms

집계 결과 성능 회귀 전체가 하층 챔버 안에서 발생했음을 알 수 있습니다(P50에서 +14.5 ms 증가, 매 프레임 12 ms 스핀 처리와 오버헤드에 부합). 그 외 순회 경로에서는 프레임 타임 변화가 없습니다.

측정 시 유의할 점

이 방식을 적용할 때는 다음 사항에 유의하세요.

  • 에디터와 캡처 부하: 이번 측정은 Unity 에디터의 Play 모드에서 실행되었으며 Cluewren이 에이전트를 위해 프레임을 캡처합니다. 두 빌드 모두 P95가 약 53 ms인 것은 이 에디터 및 캡처 오버헤드 때문이며 플랫폼은 WindowsEditor로 기록됩니다. 이 측정은 동일한 조건에서 빌드 간의 상대적인 비교에 사용해야 하며, 실제 플레이어 환경의 절대적인 성능 수치로 다루지 않습니다. 독립 실행형 플레이어 빌드 측정은 게임 최적화를 실기기에서 검증하기를 참조하세요.
  • 동일한 조건 유지: 두 빌드 모두 동일한 지시, 동일한 머신, 동일한 웜 하네스를 사용해 측정 조건을 일치시켜야 합니다.
  • API 키 관리: Ingest 키는 게임을 실행하는 환경 변수에만 설정하고, 에이전트 대화창에는 절대 붙여넣지 마세요(Cluewren 자체도 TypeSafe 키에 대해 이 규칙을 따릅니다).

정리

에이전트가 두 빌드에서 같은 지시를 수행하고 Framedash가 프레임 타임을 기록합니다. perf-diff 명령과 대시보드의 Regression 페이지로 어느 빌드가 느려졌는지 확인하고, 위치 표본 쿼리로 레벨 내 어느 위치에서 회귀가 일어났는지 특정할 수 있습니다. Cluewren에 대한 자세한 정보는 Cluewren 공식 웹사이트를 참조하세요.