SYDESYDE
SYDESYDE
  • 피드
  • 쇼케이스
  • 블로그
  • 모임
SYDESYDE© 2026 SYDE. All rights reserved.
SYDE 소개문의하기
Kakao
SYDE 오픈채팅
Instagram
Instagram
Threads
Threads
커뮤니티 가이드라인협업문의이용약관개인정보처리방침
커뮤니티 가이드라인협업문의이용약관개인정보처리방침

© 2026 SYDE. All rights reserved.

We are SYDERS

주체적인 삶으로 가득한 세상,
1400+명의 SYDER와 함께해요.

지금 주목받는 프로젝트 🔥

ⓒ 2026. SYDE

KakaoInstagramThreads
banner_instagram
하루들
이 주의 SYDE Pick

하루들

우리끼리 통하는 네컷만화

사이드프로젝트 실시간 피드 - SYDE 피드

지금 주목받는 프로젝트 🔥

User Avatar
무슨 생각을 하고 계신가요?
ᄅᄅᄅ

ᄅᄅᄅ님이 쇼케이스를 등록했어요· 14시간 전

Showcase thumbnail

Pinch — 핀치

장소에 음악과 감정을 남기고, 서로의 순간을 발견하는 음악 지도

Do Young

Do Young님이 블로그 글을 발행했어요· 15시간 전

새벽 3시에 결제까지 눌러보는 봇을 만들었는데, 첫날 6초 만에 죽었습니다

두 번째 글입니다. 이번엔 인턴으로 일하는 회사에서 겪은 일이라 엄밀히 사이드프로젝트는 아닙니다. 다만 "혼자 만든 자동화가 아무도 모르게 죽어 있었다"는 이야기라, 혼자 서비스를 굴리시는 분들께 오히려 더 가까운 사고일 것 같아 가져왔습니다.무슨 일이 있었나요커머스 프론트엔드에 Playwright E2E를 붙이고 GitHub Actions로 자동화했습니다. 매일 새벽 3시에 브라우저 4종이 상품을 담고 주문서까지 들어가서 118개를 검사하는 구조였고, 머지하고 뿌듯하게 잤습니다.다음 날 아침에 보니 6초 만에 빨갛게 죽어 있었습니다. 앱 버그였으면 차라리 나았을 텐데, 범인은 제가 방금 만든 파이프라인 본인이었습니다.제가 만든 것PR을 막는 smoke(크로미움 1종)와 새벽에 도는 full(크로미움 / WebKit / Pixel 7 / iPhone 14) 두 트랙으로 분리Cypress 대신 Playwright 선택 — 가장 오래 괴롭힌 버그가 Safari 전용이었고, 병렬 실행이 무료라서공식 컨테이너 이미지로 CI 시간 12분 47초 → 2분 59초실제 결제가 나가지 않도록 PG 요청을 두 겹으로 차단하고, "PG 요청 0건"을 단언하는 테스트까지 추가테스트 짜다가 장바구니 레이스 컨디션과 접근성 누락, 앱 버그 두 개를 덤으로 발견범인은 물음표 두 개였습니다schedule 트리거에는 inputs가 없어서 BASE_URL에 빈 문자열이 들어왔고, ??는 빈 문자열을 그대로 통과시킵니다. 그래서 앱 서버가 아예 안 뜬 채로 테스트가 출발했습니다. 고친 건 ??를 ||로 바꾼 글자 하나입니다.더 창피한 건 그다음이었습니다. 실패하면 Slack으로 알리는 스텝이 이미 있었는데, 웹훅을 Secrets에 등록하지 않아서 "웹훅이 없으면 조용히 성공"하도록 제가 짜둔 코드가 초록 체크를 찍고 끝나 있었습니다. 알림이 안 온 게 아니라, 알림이 안 왔다는 사실조차 안 알려준 겁니다.기억에 남는 것가져가실 건 세 줄입니다.유닛 테스트가 전부 초록이어도 결제는 깨집니다. 그 층을 보는 건 E2E뿐입니다.환경변수엔 ?? 말고 ||. CI가 빈 문자열을 주입하는 경로는 생각보다 많습니다.알림 없는 야간 자동화는 그냥 안 도는 것과 같습니다. 오늘 한 번 일부러 깨뜨려서 알림이 진짜 오는지 확인해 보시길 권합니다.Playwright와 Cypress를 비교한 근거, smoke/full을 나눈 기준, 하이드레이션 대기 같은 튜토리얼에 없는 함정들, 그리고 Slack 알림을 다시 설계한 과정까지 자세한 이야기는 velog에 정리했습니다.👉 전체 글 보러 가기 (velog)https://velog.io/@kdy9883/laywright-nightly-e2e다음 글로 준비 중인 소프트뱅크 해커톤 후기는 10월 중순 이후에 올리겠습니다. ?? 때문에 당해보신 분, "알고 보니 계측이 꺼져 있었다" 류의 경험 있으신 분은 댓글로 나눠 주세요.

Blog thumbnail
mustardoo

mustardoo님이 블로그 글을 발행했어요· 15시간 전

AI 게임을 만들겠다고 시작했는데, 여기서 멈추기로 했다 — UNSPECIFIED #완

지난 글의 마지막에는 이런 질문을 남겼다.이 실험을 계속할 이유가 있는가?결론부터 말하면, 여기서 멈추기로 했다.더 해볼 방법이 없었던 것은 아니다.현재 CPU-only로 진행한 추론을 GPU에 offload할 수도 있었고, GPU를 전제로 더 큰 모델을 시험할 수도 있었다. Fine-tuning이나 여러 단계의 자기 수정, Cloud Model도 남아 있었다.방법은 많다.그런데 그 전에 다시 봐야 할 게 있었다.나는 원래 무엇을 확인하려고 이 게임을 만들기 시작했는가?목표는 ‘게임에서 LLM을 돌리는 것’이 아니었다UNSPECIFIED를 시작하면서 확인하고 싶었던 질문은 단순히“작은 Local LLM을 게임에서 실행할 수 있는가?”가 아니었다.Local Model을 실행하고, 요청을 보내고, 결과를 받아 게임에 연결하는 것 자체는 결국 엔지니어링 문제에 가깝다.실제로 이번 프로젝트에서도 그 부분은 동작했다.내가 확인하고 싶었던 건 그다음이었다.작은 Local LLM이 플레이어의 행동을 읽고, deterministic한 방식만으로는 쉽게 얻기 어려운 다양하고 납득 가능한 전투 능력을 만들어낼 수 있는가?그리고 그 차이가 Local LLM을 게임에 포함하는 비용을 감수할 정도로 큰가?즉 성공 기준은 “AI가 돌아갔다”가 아니었다.제품에 넣으려면 최소한 몇 가지 조건을 동시에 만족해야 했다.기준보고 싶었던 것실행 가능성실제 게임 규칙으로 컴파일하고 실행할 수 있는가다양성비슷한 결과 하나만 반복하지 않는가플레이 적합성실제 플레이어 행동에서 나온 결과처럼 느껴지는가납득 가능성결과를 보고 왜 나왔는지 이해할 수 있는가비용추론 시간과 Runtime 복잡성을 감수할 가치가 있는가하나만 만족해서는 부족했다.다양하지만 대부분 실행할 수 없다면 게임에 넣기 어렵다.잘 작동하지만 간단한 규칙 기반 방식과 별 차이가 없다면 굳이 모델을 함께 배포할 이유가 없다.그래서 마지막 판단 기준도 자연스럽게 바뀌었다.LLM을 사용할 수 있는가?가 아니라LLM을 사용해야 할 이유를 증명했는가?앞선 실험에서 확인한 두 가지지난 글에서 자세히 다뤘기 때문에 실험 과정 전체를 다시 반복하지는 않으려고 한다.결과를 요약하면 두 가지였다.먼저 모델에게 이미 검증된 스킬 중 하나를 선택하게 했을 때는 기술적으로 안정적으로 동작했다.구조화된 Ranking 결과가 정상적으로 돌아왔고, 그 결과로 선택된 기존 스킬을 실제 게임에 적용하는 흐름도 동작했다.하지만 작은 개발자 파일럿에서는 규칙 기반 시스템과 같은 선택이 나오는 경우가 많았고, Local Model의 추가 비용을 정당화할 만큼 뚜렷한 차이는 확인하지 못했다.그래서 모델의 역할을 넓혀 스킬 자체를 조합하게 했다.처음에는 내부 구조까지 직접 작성하게 했지만 결과가 너무 낮아, 혹시 실험 자체가 불공정한 것은 아닌지 다시 의심했다.결국 마지막에는 내부 배선을 전부 코드가 맡고, 모델은 행동 순서·대상 관계·공간 관계·효과 같은 의미 구조만 결정하도록 문제를 다시 단순화했다.그래도 핵심 문제는 남았다.마지막 실험 결과 요약최종 개발용 데이터셋은 24개의 고정된 상황으로 구성했다.20개는 실제 스킬 생성이 가능한 상황이었고, 4개는 행동 증거가 부족해 아무것도 만들지 않는 것이 정답인 상황이었다.생성 가능한 경우에는 시스템마다 서로 다른 후보 3개를 요구했기 때문에, 각 시스템은 총 60개의 후보를 만들었다.방식실행 가능한 후보 / 60중복 제거 후 후보하나라도 성공한 상황 / 20규칙 기반606020Qwen3 1.7B000Qwen3.5 2B644Granite 4.2 3B000여기서 실행 가능하다는 것은 재미있다는 뜻이 아니다.게임의 의미 규칙과 Compiler 검증을 통과해 실제 능력으로 실행할 수 있었다는 뜻이다.Local Model 중에서는 2B 모델만 일부 결과를 통과시켰고, 중복을 제거한 4개 역시 구조적으로는 한 종류로 수렴했다.세 모델 모두 한 상황에서 요구했던실행 가능하면서 서로 다른 세 가지 후보를 만든 경우는 없었다.그래서 마지막 Composition 단계는 추가 사람 평가로 넘어가지 않았다.사람에게 어떤 결과가 더 재미있는지 묻기 전에, 비교 가능한 후보 자체를 안정적으로 생성할 수 있어야 한다고 판단했기 때문이다. 성공한 몇 개만 골라 평가하면 실제 제품 성능보다 좋아 보일 가능성도 있었다.그리고 마지막 실패의 핵심은 단순한 JSON 형식 문제가 아니었다.세 Local Model이 만든 총 180개 후보 중 150개는 첫 번째 거절 사유가 행동·물체·대상 사이의 의미 관계 일관성 문제였다.예를 들어 물체 정보가 없는 행동을 선택한 뒤 “첫 번째 행동과 같은 물체”라는 조건을 붙이거나, 대상이 없는 행동에서 이전 공격 대상을 참조하는 식이었다.문법에 맞는 구조화된 출력을 만드는 것과, 여러 조건이 동시에 성립하는 게임 규칙을 만드는 것은 다른 문제였다.각각의 선택은 그럴듯해도, 스킬 전체의 관계를 끝까지 일관되게 유지하는 데서 문제가 생겼다.그런데 규칙 기반 시스템은 어디까지 갈 수 있었을까?여기서 비교를 위해 만든 deterministic Composer가 예상보다 중요한 결과를 하나 보여줬다.모든 관련 행동 증거가 충분히 강하게 존재한다고 가정한 개발용 테스트 공간에서,38,068개의 조합 표현을 검사했고, 구조 기준으로 6,434개의 서로 다른 유효 조합을 찾을 수 있었다.38,068개 검사 → 6,434개의 서로 다른 유효 구조이 숫자를“재미있는 스킬이 6,434개 생겼다.”라고 해석하면 안 된다.실제로 재미있는지, 밸런스가 맞는지, 플레이어에게 줄 가치가 있는지는 별개의 문제다.한 플레이어에게 이 모든 스킬이 동시에 유효하다는 뜻도 아니다.하지만 중요한 건 숫자의 크기 자체가 아니었다.현재 만든 Skill Grammar의 유효한 조합 공간을 deterministic code만으로도 상당 부분 탐색할 수 있었다는 것이었다.이 사실이 이후 판단에 꽤 큰 영향을 줬다.모델을 안전하게 만들수록, 생성의 핵심이 코드로 이동했다Local Model의 유효성을 올리는 방법은 분명히 있다.예를 들어 첫 번째 행동을 고른 뒤에는 그 행동과 실제로 연결 가능한 두 번째 조건만 모델에게 보여줄 수 있다.아예 가능한 구조들을 코드가 먼저 생성한 뒤 모델에게 그중 하나를 선택하게 할 수도 있다.잘못된 결과를 코드가 자동으로 수정할 수도 있다.단계별로 합법적인 선택지만 허용하는 식으로 더 강하게 제한할 수도 있다.이런 방식을 사용하면 실행 가능성은 분명 좋아질 수 있다.그런데 그럴수록 다른 문제가 생긴다.게임 코드가 이미무엇이 가능한 행동인지 계산하고어떤 관계가 합법적인지 판단하고유효한 조합을 찾아내고잘못된 조합을 수정한다면모델에게 남는 일은 점점 줄어든다.결국 다시“이미 안전한 후보 중 어떤 걸 선택할까?”라는 문제로 돌아간다.그리고 그 문제에서는 앞선 실험에서 규칙 기반 시스템보다 뚜렷한 추가 가치를 확인하지 못했다.특히 deterministic Composer만으로도 현재 Grammar의 유효 조합 공간을 상당히 탐색할 수 있다는 걸 확인한 뒤에는 이 문제가 더 선명해졌다.모델을 안전하게 만들기 위해 선택 공간을 코드가 더 많이 좁힐수록, 그 선택 공간 자체를 만드는 일도 코드가 하고 있었다.이게 더 진행하지 않기로 한 가장 큰 이유 중 하나였다.속도는 단독 실패 원인이 아니었다측정 환경: AMD Ryzen 9 9900X, CPU-only inference, 6 threads, Q4_K_M quantization, context 8192.Local Model의 추론 시간도 측정했다.Cold Load와 초기 Warm-up 시간은 제외한 값이다.이 수치는 모델 자체의 보편적인 성능 벤치마크가 아니라, 내가 사용한 개발 환경에서의 제품 판단용 측정값이다.모델최소중앙값최대Qwen3 1.7B13.93초14.32초15.96초Qwen3.5 2B17.76초20.37초22.26초Granite 4.2 3B33.27초38.46초40.35초규칙 기반 Composer는 같은 개발 환경에서 중앙값 약 2.53초였다.다만 이것 역시 현재 조합 공간을 폭넓게 탐색하는 개발용 구현이다.Shipping 최적화를 한 값은 아니고, 가능한 구조를 미리 계산하거나 캐시하면 더 줄일 수 있는 성격의 비용이다.그리고 14~20초대의 Local Model 추론 자체를 무조건 사용할 수 없는 시간이라고 생각한 것도 아니다.Adaptive Skill이 자주 등장하지 않는다면 전투 사이에서 비동기로 돌리고 다음 보상 시점까지 결과를 준비할 수도 있다.문제는 단순히 느렸다는 게 아니었다.그 시간을 기다릴 만큼 결과가 더 좋아지지 않았다.Local Model을 실제 제품에 포함하면 추론 시간 이외에도 모델 파일 배포, Worker 관리, Warm-up, 메모리 예산, Timeout, 오래된 응답 폐기, Fallback, 이후 기기별 검증 같은 비용이 추가된다.그 비용은 결과가 확실히 좋아진다면 감수할 수 있다.이번에는 그 근거를 얻지 못했다.“그럼 더 큰 모델을 쓰면 되지 않을까?”당연히 가능한 선택이다.이번 프로젝트는“LLM으로 게임 메커닉을 만드는 것은 불가능하다.”를 증명한 게 아니다.7B나 14B급 Local Model은 시험하지 않았다.Cloud Model도 시험하지 않았다.Fine-tuning이나 LoRA도 하지 않았다.GPU inference 역시 최종 제품 조건으로 비교하지 않았다.여러 번 생성하고 스스로 결과를 고치는 Agent 형태도 시험하지 않았다.더 많은 플레이 데이터를 모델에게 제공하는 방법도 남아 있다.따라서 이런 방법들이 실패한다고 말할 수는 없다.다만 여기서 중요하게 본 건 다른 질문이었다.더 좋은 모델이 존재할 수 있는가가 아니라, 내가 처음 세운 제품 제약 안에서 이 접근이 성립하는가?UNSPECIFIED에서 원했던 건 작은 Local Model이 상업 게임 안에서 실제로 돌아가면서 플레이어 행동을 읽고 새로운 전투 규칙을 만들어주는 것이었다.7B나 14B 모델이 필요하다면 그건 새로운 비용 구조를 가진 실험이다.Cloud API가 필요하다면 Local이라는 전제가 바뀐다.Fine-tuning이 필요하다면 별도의 데이터와 학습 프로젝트가 된다.여러 번의 Repair Pass가 필요하다면 Runtime 비용과 지연 역시 달라진다.모두 충분히 연구할 가치가 있을 수 있다.하지만 이번 가설을 살리기 위해 성공 조건을 계속 옮기는 것과는 구분하고 싶었다.실패할 때마다 조건을 바꾸면 가설은 절대 실패하지 않는다R&D를 계속할 수 있는 이유는 언제나 만들 수 있다.이번 모델이 작았다.입력 데이터가 부족했다.Prompt를 더 다듬을 수 있다.다음 모델은 더 좋을 것이다.Constrained Decoding을 붙이면 된다.Tool Use를 붙이면 된다.각각 맞는 말일 수 있다.그런데 실패가 나올 때마다 성공할 때까지 조건을 하나씩 바꾼다면,어느 순간부터는 처음 질문을 검증하는 것이 아니라AI를 성공시키기 위한 새로운 문제를 계속 만드는 것에 가까워진다.이번에는 그 선을 여기로 잡았다.최종적으로 내릴 수 있는 결론은 아주 제한적이다.현재 시험한 1.7B~3B급 Quantized Local Model과 현재의 Skill Grammar, Single-pass 실행 조건에서는 deterministic 방식보다 의미 있게 나은 Adaptive Skill 생성 가치를 확보하지 못했다.그 이상도, 그 이하도 아니다.하지만 이 정도면 다음 개발 비용을 결정하기에는 충분했다고 생각한다.불가능해서 멈춘 게 아니라, 현재 가설에 더 많은 개발 비용을 투자할 근거가 사라졌기 때문에 멈췄다.그래도 이 프로젝트에서 얻은 건 꽤 많았다Local LLM이라는 핵심 가설은 원하는 결과를 얻지 못했다.그렇다고 이번 개발 전체가 버려지는 건 아니다.오히려 가설을 제대로 검증하려다 보니 AI와 상관없이 재사용할 수 있는 시스템이 꽤 많이 남았다.Deterministic Combat대시와 피격, 동시에 발생한 공격, 투척과 피격처럼 순서가 민감한 전투 상황을 엔진 Callback 순서에 맡기지 않고 하나의 판정 흐름에서 처리하는 구조를 만들었다.Held Object Combat월드의 물체를 집고, 근접 공격에 사용하고, 던지고, 부수고, 튕기고, 다시 받아내는 흐름을 하나의 공통 전투 구조로 만들었다.Semantic Telemetry와 Motif단순히 버튼을 몇 번 눌렀는지가 아니라,플레이어가 실제로 확정한 행동과 그 행동 사이의 관계를 측정할 수 있게 됐다.Skill Grammar완성된 스킬을 하나씩 만드는 대신,행동·대상·기억·공간·효과를 조합해서 능력을 표현할 수 있는 작은 게임 언어를 만들었다.Skill Compiler와 실행 안전장치조합된 능력이 실제로 존재하는 기능만 사용하는지,입출력이 맞는지,상태의 수명이 정의돼 있는지,순환 실행이 없는지,파생 효과가 무한히 늘어나지 않는지를 검사할 수 있게 됐다.Presentation Pipeline실제 게임에서 실행되는 능력과 플레이어에게 보여주는 설명이 같은 검증된 의미 구조에서 나오도록 만들었다.Deterministic ComposerLLM 없이도 현재 Grammar의 가능한 조합을 탐색하고 비교할 수 있는 기준선이 생겼다.Local Model / Benchmark Infrastructure외부 Local Worker의 시작과 종료, Warm-up, Timeout, Crash, 늦은 응답, Fallback뿐 아니라 어떤 소스 상태에서 어떤 결과가 만들어졌는지도 추적할 수 있는 실험 구조를 만들었다.돌아보면 조금 재미있는 결과다.LLM을 검증하기 위해 만든 시스템 상당수가, LLM이 없어도 쓸 수 있는 자산으로 남았다.실패한 데이터도 같이 남기기로 했다코드만 보존할 생각은 없다.최종 Benchmark에 사용한 고정된 테스트 사례와 모델 결과, 실패 이유, 실행 시점의 Source 상태도 함께 보존하기로 했다.이유는 크게 세 가지다.1. 나중에 같은 실험을 다시 처음부터 하지 않기 위해몇 달이나 몇 년 뒤 훨씬 좋은 Small Local Model이 나온다면 다시 시험해볼 수 있다.그때 현재 테스트 데이터를 그대로 사용하면모델만 바뀌었을 때 실제로 얼마나 달라졌는가를 볼 수 있다.반대로 모델도 바꾸고, Prompt도 바꾸고, Grammar도 바꾸고, 입력 데이터도 바꾼다면 무엇 때문에 결과가 좋아졌는지 알기 어렵다.2. ‘안 됐다’가 아니라 ‘어디서 안 됐는지’를 남기기 위해시간이 지나면 실패 과정은 쉽게 압축된다.몇 달 뒤에는 아마“작은 모델로 해봤는데 잘 안 됐지.”정도로 기억하기 쉽다.하지만 실제로는 조금 더 구체적이었다.좁은 Ranking 역할에서는 모델의 추가 가치가 작았다.실제 Composition을 열자 실행 가능성이 무너졌다.내부 배선 부담을 없앤 뒤에도 의미 관계 일관성 문제가 남았다.그리고 마지막 180개 후보 중 150개는 첫 번째 거절 사유 기준으로 그 문제에서 탈락했다.이런 정보가 남아 있어야 다음 실험에서 똑같은 길을 다시 걷지 않는다.3. Negative Result도 다음 의사결정을 위한 데이터이기 때문에처음에는 나도 이런 기대가 있었다.Structured Output을 강하게 제한하고 뒤에 Compiler까지 붙이면, 작은 모델도 꽤 안정적으로 게임 메커닉을 만들 수 있지 않을까?그리고 2B가 부족하다면3B 정도면 꽤 좋아지지 않을까?라는 기대도 있었다.이번 조건에서는 둘 다 확인되지 않았다.이것 역시 결과다.성공한 실험만 남기고 실패한 결과를 지워버리면 다음에는 다시 같은 가정을 처음부터 검증해야 한다.그래서 이번 결과는 “버려진 프로토타입”보다는 다음 판단을 위한 기준점으로 남겨두려고 한다.이번 프로젝트에서 가장 크게 배운 것돌이켜보면 기술 자체보다 몇 가지 판단 기준을 더 많이 얻은 것 같다.Structured Output과 Semantic Validity는 다르다.JSON Schema를 만족시키는 것만으로 실제 실행 가능한 규칙이 되는 건 아니었다.국소적으로 그럴듯한 선택과 전체적으로 일관된 규칙도 다르다.행동 하나, 대상 하나를 고르는 건 잘해도 여러 단계의 제약을 동시에 유지하는 건 훨씬 어려웠다.AI의 자유도와 안전성은 공짜로 동시에 얻어지지 않았다.자유도를 늘리면 Invalid Space가 빠르게 커졌고, 안전성을 높이기 위해 제약을 강화하면 중요한 생성 로직이 Host 쪽으로 이동했다.그리고 무엇보다,Baseline을 만드는 일이 중요했다.규칙 기반 비교 대상이 없었다면 모델이 무언가를 생성했다는 사실 자체를 성공으로 착각했을 수도 있다.마지막으로 하나 더 있다.더 해볼 수 있다는 것과, 더 해야 한다는 것은 다르다.이번 프로젝트에서는 그 차이가 가장 중요한 결과였던 것 같다.UNSPECIFIED를 여기서 끝내며UNSPECIFIED는 처음부터 AI가 반드시 성공해야 하는 프로젝트로 만들고 싶지는 않았다.실제로 가능한지 알고 싶었다.그래서 AI를 넣기 전에 액션게임을 만들었고,플레이어 행동을 측정했고,그 행동을 능력으로 표현할 수 있는 언어를 만들었고,실행 가능한지 검증할 Compiler와 Runtime을 만들었고,마지막에는 규칙 기반 방식과 실제 Local Model을 같은 조건에서 비교했다.그리고 결과는 처음 기대했던 것과 달랐다.조금 아쉽기는 하다.하지만 가설이 마음에 든다는 이유로 계속 개발 비용을 쓰는 것보다는,처음 세운 질문에 지금까지 얻은 데이터가 어떤 답을 하고 있는지 따르는 편이 맞다고 생각한다.UNSPECIFIED는 내가 처음 기대했던 형태의 게임이 되지는 못했다.대신 “작은 Local LLM으로 실제 게임 규칙을 만들 수 있을까?”라는 질문에는, 적어도 내가 정한 조건 안에서 끝까지 답해볼 수 있었다.가설은 실패했고, 질문에는 답했다.

Blog thumbnail
mustardoo

mustardoo님이 블로그 글을 발행했어요· 15시간 전

Local LLM을 게임에 실제로 넣어봤더니 — UNSPECIFIED #4

지난 글에서는 AI에게 스킬을 만들게 하려다가, 오히려 먼저 게임이 스킬을 표현할 수 있는 언어부터 만들게 된 이야기를 했다.플레이어가 어떤 행동을 반복하는지는 게임이 계산한다.그 행동을 실제 능력으로 표현할 수 있는 문법도 만들었다.잘못된 조합이 들어오면 걸러낼 Compiler도 만들었다.그리고 실제 효과와 플레이어가 읽는 설명 역시 같은 검증 결과에서 나오도록 했다.여기까지 만들고 나니 드디어 처음부터 해보고 싶었던 질문을 시험할 수 있게 됐다.이 조합 문제를 Local LLM이 규칙 기반 방식보다 실제로 더 잘 풀 수 있을까?이번에는 아이디어 수준에서 끝내고 싶지 않았다.그래서 실제 Local Model을 게임에 연결하고, 같은 플레이 데이터를 규칙 기반 시스템과 모델에게 넣어 직접 비교하기 시작했다.결과는 처음 예상했던 것과 꽤 달랐다.먼저, 정말 게임 안에서 Local LLM을 돌렸다비교에는 세 종류의 quantized Local Model을 사용했다.구분모델작은 모델Qwen3 1.7B2B급 모델Qwen3.5 2B3B급 모델Granite 4.2 3B비교 기준Deterministic 규칙 기반 시스템최종 비교에서는 같은 CPU 환경과 같은 입력 조건을 사용했고, 결과가 흔들리지 않도록 temperature를 0으로 두고 fixed seed를 사용했다.모델이 실패했을 때 게임 자체가 멈추는 구조도 아니었다.Local worker가 정상적으로 준비됐는지 확인하고, 요청이 늦거나 잘못된 결과가 오면 폐기하고, 사용할 수 없는 결과라면 규칙 기반 결과로 돌아가도록 했다.즉 이번 실험은 프롬프트 창에 플레이 로그를 넣어보고 답변을 읽는 식이 아니었다.실제로 게임에서 사용할 수 있는 파이프라인 안에 Local LLM을 넣은 상태에서 비교했다.그리고 가장 먼저 모델에게 준 일은 생각보다 단순했다.첫 번째 시도 — 이미 완성된 스킬 중에서 골라보게 했다처음부터 모델에게 스킬 전체를 만들게 하지는 않았다.그건 너무 많은 변수를 한꺼번에 시험하는 것 같았다.그래서 먼저 실제 게임에서 이미 검증된 몇 개의 Adaptive Skill을 준비했다.각 스킬은 이미 Compiler를 통과했고, 실제 전투에서 실행할 수 있고, UI에도 정상적으로 설명할 수 있는 상태였다.모델에게는 플레이어의 Motif를 보여주고 이렇게 묻는 셈이었다.“현재 플레이어에게 가장 잘 맞는 능력은 무엇인가?”규칙 기반 시스템에도 똑같은 정보를 줬다.규칙 기반 쪽은 어떤 Motif가 얼마나 반복됐는지, 최근 같은 스킬이 계속 선택되지는 않았는지 등을 보고 순위를 정한다.Local LLM은 같은 후보들을 보고 의미적으로 가장 잘 맞는 순서를 고른다.기술적으로는 꽤 잘 작동했다.모델 호출도 됐고, 구조화된 결과도 정상적으로 돌아왔고, 실제 게임에 적용할 수 있었다.그런데 정작 비교해보니 다른 문제가 보였다.규칙 기반 시스템과 모델이 같은 선택을 하는 경우가 많았다.물론 다른 선택이 나온 경우도 있었다.하지만 당시 사람 비교는 작은 개발자 파일럿이었고, 일부는 시스템이 제대로 작동하는지 확인하기 위한 guided sanity test에 가까웠다.그래서 이 결과를 가지고“규칙 기반과 LLM이 동급이라는 것이 증명됐다.”라고 말할 수는 없다.다만 한 가지 신호는 꽤 분명했다.추가 Local Model 비용을 정당화할 정도로 뚜렷한 차이는 보이지 않았다.생각해보면 이상한 결과도 아니었다.어떤 능력이 사용 가능한지는 이미 코드가 정하고 있었다.Motif의 강도도 코드가 계산했다.후보 스킬은 전부 실행 가능한 상태였다.최근 어떤 스킬을 사용했는지도 코드가 알고 있었다.이 상태에서 모델에게 남은 일은 결국“이미 정리된 후보 중 어떤 게 조금 더 어울려 보이는가?”에 가까웠다.그러다 보니 이런 의문이 생겼다.혹시 내가 LLM에게 너무 쉬운 일만 시키고 있는 건 아닐까?그래서 모델에게 더 많은 권한을 주기로 했다.두 번째 시도 — 그럼 스킬 자체를 조합하게 해보자이번에는 완성된 능력 중 하나를 고르는 게 아니라,스킬 구조 자체를 모델이 만들도록 했다.앞 글에서 만든 Skill Grammar를 실제로 사용했다.어떤 행동에서 시작하는지.무엇을 기억하는지.어떤 대상과 연결하는지.어떤 효과로 끝나는지.모델이 이런 구조를 직접 조립하도록 한 것이다.여기까지가 애초에 Local LLM을 사용하고 싶었던 이유와 훨씬 가까웠다.사람이 완성된 스킬을 전부 만들어놓고 고르게 하는 게 아니라,사람이 만든 게임 언어 안에서 모델이 새로운 조합을 만드는 것.그런데 결과는 좋지 않았다.각 시스템에 여러 상황을 넣고, 상황마다 세 개의 스킬 후보를 만들게 했다.규칙 기반 시스템은 60개의 후보를 전부 실제 게임에서 사용할 수 있는 구조로 만들었다.Local Model 세 종류는 모두 60개 중 0개였다.방식실제 파이프라인을 통과한 후보규칙 기반60 / 60Local 1.7B0 / 60Local 2B0 / 60Local 3B0 / 60처음 보면 꽤 강한 결과처럼 보인다.그런데 나는 여기서 실험을 끝내지 않았다.왜냐하면 모델에게 시킨 일을 다시 보니 조금 불공정하다고 느꼈기 때문이다.실패한 이유가 정말 ‘스킬을 못 만들어서’였을까?당시 모델은 의미 있는 스킬을 생각하는 것뿐 아니라,게임 내부에서 사용하는 꽤 낮은 수준의 연결 구조까지 직접 작성해야 했다.어떤 node를 만들지.어떤 기능의 어떤 입력 형식을 사용할지.앞 node의 어떤 출력을 다음 node에 연결할지.이런 내부 배선까지 모델이 직접 맞춰야 했다.실제로 실패 결과를 보면 존재하지 않는 연결 방식을 사용하거나, 필요한 입력을 빼먹거나, 잘못된 node를 참조하는 문제가 많이 나왔다.일부 출력은 길어지면서 잘리기도 했다.그러니까 이 결과만 가지고“작은 Local LLM은 게임 스킬을 설계하지 못한다.”라고 결론 내리기는 어려웠다.스킬의 의미를 설계하는 능력과,Compiler 내부의 배선을 정확하게 작성하는 능력을 동시에 시험하고 있었기 때문이다.그래서 한 번 더 구조를 바꿨다.이번에는 모델에게 내부 구현을 최대한 숨겼다.세 번째 시도 — 내부 배선은 코드가 하고, 모델은 ‘의미’만 고르게 했다마지막 실험에서는 모델이 더 이상 node나 내부 연결을 작성하지 않았다.대신 이런 것만 결정하게 했다.어떤 행동들이 어떤 순서로 이어지는지.같은 물체인지, 다른 물체인지.같은 대상인지, 다른 대상인지.어느 시점의 위치를 사용할지.한 지점에서 효과를 만들지, 두 위치의 중간을 사용할지.어떤 종류의 효과로 끝낼지.그리고 어떤 Motif를 이 스킬의 근거로 삼았는지.예를 들어 모델 입장에서는 이런 수준의 의미만 결정하면 된다.대시를 한다→ 이후 근접 공격을 성공시킨다→ 대시 시작 위치와 적중 위치를 연결한다→ 두 위치 사이에 효과를 만든다게임 내부에서 이걸 어떤 Skill Block으로 연결하고, 어떤 입력과 출력을 사용해야 하는지는 전부 코드가 대신 처리한다.모델이 정한 의미 구조를 게임이 실제 Skill Graph로 변환한 뒤,이전과 똑같은 Compiler, Runtime, Presentation을 통과시켰다.중요한 건 여기서 게임 규칙을 느슨하게 만든 게 아니라는 점이다.모델이 작성해야 하는 표현만 단순하게 만들었다.기존 방식과 의미가 달라지지 않았는지도 전체 조합 공간을 대상으로 따로 확인했다.이제 적어도“내부 배선이 너무 복잡해서 모델이 실패했다.”라는 이유는 상당 부분 걷어낸 셈이었다.그래도 결과는 크게 달라지지 않았다마지막 비교에서는 실제 스킬 생성이 가능한 20개의 상황을 준비했다.각 시스템은 한 상황마다 서로 다른 세 개의 후보를 만들도록 했다.즉 시스템 하나당 총 60개의 후보를 생성한다.결과는 다음과 같았다.방식파이프라인 통과중복 제거 후 후보하나라도 성공한 상황규칙 기반60 / 606020 / 20Local 1.7B0 / 6000 / 20Local 2B6 / 6044 / 20Local 3B0 / 6000 / 20여기서 ‘통과했다’는 것은 재미있는 스킬이라는 뜻이 아니다.게임의 규칙과 제약을 만족해서 실제로 컴파일하고 실행할 수 있는 구조였다는 뜻이다.2B 모델만 일부 결과를 통과시켰다.하지만 6개의 통과 결과 중 중복을 제거하면 4개가 남았고,그 4개도 구조적으로 보면 사실상 같은 형태 하나였다.세 모델 중 어느 것도 한 상황에서 요구했던실행 가능하면서 서로 구조적으로 다른 세 가지 후보를 만들어내지는 못했다.여기서부터는 단순히 출력 형식 문제가 아니었다.문제는 JSON이 아니었다마지막 실험에서는 모델이 JSON을 못 만들어서 실패한 경우가 핵심이 아니었다.겉으로 보면 출력은 멀쩡했다.필요한 필드도 있고,허용된 값도 사용했고,정해둔 구조도 대체로 맞췄다.그런데 각각의 선택을 서로 연결해서 보면 모순이 생겼다.예를 들어 이런 식이다.물체 정보가 존재하지 않는 행동을 선택해놓고“첫 번째 행동과 같은 물체여야 한다”는 조건을 붙인다.또는,대상이 없는 행동을 골라놓고“이전에 공격한 대상과 같은 대상”을 참조한다.한 단계씩 따로 보면 각각은 그럴듯하다.하지만 스킬 전체를 보면 동시에 성립할 수 없다.최종 생성된 180개의 Local Model 후보 중 150개가 이런 종류의 의미 관계 모순에서 막혔다.즉 작은 모델들이 가장 어려워한 건올바른 JSON을 만드는 것보다는,스킬 전체에 걸쳐 여러 조건을 동시에 일관되게 유지하는 것에 가까웠다.이 차이가 생각보다 컸다.문법에 맞는 구조를 만드는 능력과, 실행 가능한 게임 규칙을 만드는 능력은 같은 것이 아니었다.그래서 권한을 어디까지 줘야 할지가 문제가 됐다여기까지 실험하고 나니 한 가지 패턴이 보였다.처음처럼 모델의 역할을 아주 좁게 만들면 안정적이다.이미 실행 가능한 스킬을 준비해놓고 그중 하나만 고르게 하면 Local LLM도 충분히 할 수 있다.그런데 그 정도로 제한하면 규칙 기반 시스템과 비교했을 때 모델이 추가로 해주는 일이 별로 없다.반대로 모델이 진짜 새로운 조합을 만들 수 있도록 자유도를 넓혀주면,이번에는 실제 게임에서 사용할 수 없는 결과가 급격히 많아졌다.결국 내가 마주친 건 이런 상황이었다.모델의 권한을 좁히면 굳이 LLM이 필요하지 않았고,권한을 넓히면 실행 가능한 결과를 안정적으로 만들지 못했다.이게 이번 실험에서 가장 크게 남은 결과였다.그렇다고 여기서 “LLM은 안 된다”는 결론은 아니다이 결과를 너무 넓게 해석하고 싶지는 않다.이번에 시험한 건 작은 Local Model이, 한 번의 추론으로, 실제 게임에서 실행 가능한 스킬 관계를 만들어내는 조건이었다.1.7B부터 3B 정도의 모델을 시험했다.그보다 훨씬 큰 모델은 시험하지 않았다.Cloud Model도 시험하지 않았다.Fine-tuning이나 LoRA도 하지 않았다.여러 번 생성한 뒤 스스로 수정하게 하는 구조도 시험하지 않았다.따라서“LLM으로 게임 메커닉을 만드는 건 불가능하다.”는 결론은 아니다.정확하게 말하면,내가 이 프로젝트에서 사용하려고 했던 크기와 실행 조건의 Local LLM은, 규칙 기반 방식보다 충분히 나은 결과를 안정적으로 보여주지 못했다.정도다.3B 모델로 조금 키워본다고 문제가 자동으로 해결되는 신호도 이번에는 보이지 않았다.오히려 마지막 실험에서는 3B 모델도 통과한 결과가 없었다.그래도 더 해볼 방법은 많았다여기서 끝까지 밀어붙이는 방법은 분명히 있었다.가능한 선택지를 코드가 더 좁혀줄 수도 있다.현재 상태에서 합법적인 조건만 모델에게 보여줄 수도 있다.잘못된 결과를 코드가 자동으로 고친 뒤 다시 Compiler에 넣을 수도 있다.모델에게 한 번에 완성된 답을 요구하지 않고 여러 단계에 걸쳐 수정하게 할 수도 있다.더 큰 모델을 사용할 수도 있다.더 많은 플레이 데이터를 모델에게 줄 수도 있다.문제는 하나씩 추가할 때마다 새로운 질문이 생긴다는 점이었다.그렇게 해서 성공한 시스템은, 내가 처음 만들고 싶었던 시스템과 여전히 같은가?AI를 안전하게 만들기 위해 가능한 선택지를 게임 코드가 거의 전부 계산해놓고,잘못된 선택은 코드가 고치고,모델에게 마지막 선택만 맡긴다면,과연 그 스킬을 만든 주체는 누구라고 해야 할까?그리고 그런 구조라면 Local LLM을 게임 안에 넣으면서 발생하는 비용을 감수할 이유가 있을까?여기까지 오고 나니 다음 질문은 더 이상 기술적인 문제가 아니었다.이 실험을 계속할 이유가 있는가?다음 글에서는 더 큰 모델이나 더 복잡한 시스템으로 계속 밀어붙이는 대신, UNSPECIFIED의 개발을 여기서 멈추기로 결정한 이유를 정리해보려고 한다.

Blog thumbnail
제이현

제이현님이 블로그 글을 발행했어요· 1일 전

텅 빈 게시판을 6개월 만에 250개로 채운 과정

syde.kr 쇼케이스 게시판에 등록된 프로젝트가 어느덧 250개를 넘었다. 사이드프로젝트로 만든 서비스가 이렇게 많을 줄은 몰랐는데, 숫자를 보고 있으면 새삼 놀랍다.250개가 되기까지 걸린 시간은 딱 6개월이었다. 이번 글에서는 텅 빈 게시판이 어떻게 채워졌는지, 그 과정을 공개해보려 한다.오픈채팅방에서 시작된 고민웹사이트를 만들기 전부터 SYDE는 오픈채팅방을 운영하고 있었다. 지금은 약 1,400명이 활발하게 이야기를 나누는 공간이다. 들어와보면 하루도 대화가 끊이지 않고 북적북적하다. 서로 응원하는 따뜻한 분위기라서 누구나 사이드프로젝트 이야기를 편하게 꺼낼 수 있다.오픈채팅방 놀러오세요!오픈채팅방을 만든 지는 3년이 넘었다. 그동안 많은 멤버가 자기 프로젝트를 올렸다. 피드백을 구하거나, 베타테스터를 모집하거나, 홍보를 하려고.문제는 카톡 오픈채팅방의 특성이었다. 한 번 올린 메시지는 휘발된다. 대화가 워낙 많이 오가다 보니, 누군가 프로젝트 이야기를 올리고 한 시간만 지나 들어와도 스크롤을 한참 올려야 겨우 찾을 수 있었다.그래서 멤버들의 프로젝트를 한곳에 모아볼 수 있는 환경이 필요하다는 생각이 들었다.내 생각이 맞는지부터 확인했다그런데 이건 어디까지나 내 생각이었다. 사람들이 정말 자기 프로젝트를 올릴 공간을 원하는지는 별개의 문제다.그래서 어떻게 검증했나?오픈채팅방에 투표를 올렸다.사이드 홈페이지가 생긴다면 어떤 기능이 가장 필요한지 물었다.압도적으로 많은 사람이 "내 프로젝트를 업로드할 수 있는 아카이빙 게시판"을 골랐다.결과를 보자마자 syde.kr 개발 기획에 바로 착수했다.개발보다 먼저 초기 유저부터커뮤니티나 플랫폼 서비스는 초기 유저를 모으는 일이 정말 어렵고 중요하다. 무작정 개발부터 시작하면 텅 빈 게시판만 보게 된다.(경험담임ㅠㅠ) 그래서 개발에 들어가기 전에 초기 유저 모집을 먼저 했다.초기 유저를 어떻게 모으냐고 묻는 사람이 많은데, 내 개인적인 팁은 단순하다. 1:1로, 진심을 담아, 한 명 한 명에게 다가가는 것이다.구체적으로 뭘 했나?그동안 오픈채팅방에 프로젝트를 올렸던 사람 중, 내 기억에 인상 깊게 남은 프로젝트를 리스트업했다.한 명 한 명에게 개인톡을 보냈다.쇼케이스 게시판을 만들 계획이라는 것, 그리고 OO님의 프로젝트가 인상 깊어서 꼭 올려주셨으면 한다는 이야기를 했다.따지고 보면 콜드 시딩이다. 이때 중요한 건 AI로 글을 "찍어내면" 안 된다는 점이다. 한 명 한 명에게 진심으로 다가가야 한다.1:1로 다가가면 대상의 숫자는 당연히 적다. 대신 그만큼 더 파워풀하고 진심인 초기 유저를 모을 수 있다. 나는 초기 유저에게 숫자는 중요하지 않다고 생각한다. 한 명의 퀄리티와 진심이 훨씬 중요하다. 그런 사람들이 다른 유저를 끌고 들어온다.초기에 “거부기린” 메이커 분께 보냈던 메시지로그인과 게시판, 그것만 있었다초기 유저를 미리 확보하면서 동시에 본격적으로 웹페이지 개발에 들어갔다. 처음에는 정말 아무것도 없었다. 로그인 기능과 쇼케이스 게시판이 전부였다. 당연히 휑해 보이는 빈 공간이었다.하지만 미리 이야기해둔 초기 유저들이 있었다. 그분들이 먼저 프로젝트를 올려주었다. 애초에 1:1로 섭외한 분들이라 숫자는 많지 않았다. 15개 정도였다.15개로 뭘 할 수 있었나?올라온 프로젝트를 SYDE 인스타그램에서 한 개씩 소개하고 홍보하기 시작했다.그걸 본 사람들이 쇼케이스 게시판으로 유입되기 시작했다.아마 초기 유저분들이 주변 사람들에게 알려준 것도 적지 않았을 것이다.그렇게 선순환이 시작됐다.결국 내가 한 일돌아보면 내가 한 일은 두 가지가 전부다. 초기 유저를 한 명 한 명 모집한 것, 그리고 우리 콘텐츠를 인스타그램에 올린 것.선순환 구조에 들어간 뒤로는 많은 사이드프로젝터가 각자의 프로젝트를 하나둘 올리기 시작했다. 그렇게 6개월 만에 250개가 넘는 프로젝트가 등록되었다.텅 빈 게시판을 채운 건 대단한 growth 전략이 아니라, 한 명 한 명에게 보낸 개인톡이었다.

Blog thumbnail
mustardoo

mustardoo님이 블로그 글을 발행했어요· 1일 전

AI에게 스킬을 만들게 하려다 게임의 언어부터 만들었다 — UNSPECIFIED #3

지난 글에서는 플레이어가 무엇을 반복해서 하는지 알아보기 위해 행동의 횟수보다 행동 사이의 관계를 기록하기 시작했다는 이야기를 했다.공격한 뒤 물체를 던지는지.던진 뒤 다른 물체를 집는지.대시한 뒤 다시 공격을 이어가는지.이런 반복되는 관계를 어느 정도 구분할 수 있게 되면서 다음 문제가 생겼다.그럼 이 습관을 실제 게임의 능력으로 어떻게 바꿀까?분석 결과를 보여주는 것만으로는 게임이 달라지지 않는다.플레이어가 계속 비슷한 방식으로 싸우고 있다면, 그 행동 자체가 새로운 전투 규칙으로 돌아오게 만들고 싶었다.처음에는 이 부분도 생각보다 단순하게 해결할 수 있을 것 같았다.처음에는 패턴 하나에 스킬 하나를 만들었다예를 들어 이런 플레이어가 있다고 해보자.물체를 던진다 ↓ 다른 물체를 집는다 ↓ 다시 근접 공격한다이 행동을 반복한다면 그 흐름을 그대로 이용하는 능력을 만들 수 있다.실제로 처음 만든 능력도 비슷했다.물체를 던져 적에게 충격을 준 뒤 잠시 그 충격을 기억한다.그다음 새로운 물체를 집거나 받아내면 기억해둔 충격을 그 물체에 연결한다.그리고 그 물체로 다음 근접 공격을 성공시키면 이전 투척의 힘 일부가 같이 전달된다.투척 적중 ↓ 충격을 기억 ↓ 새로운 물체 획득 ↓ 다음 근접 공격 강화처음 이게 실제로 동작했을 때는 꽤 만족스러웠다.단순히투척을 많이 했다 → 투척 공격력 +20%가 아니라,플레이어가 원래 하던 행동은 그대로 남아 있으면서 그 행동 사이에 새로운 연결 하나가 생겼기 때문이다.다른 방식도 만들어봤다.한 적을 때린 뒤 별개의 공격으로 다른 적을 때리면, 두 적의 위치 관계를 기억해서 둘 사이에 새로운 효과가 생기게 했다.적 A 공격 ↓ 적 B 공격 ↓ 두 위치 사이에 효과 발생여기까지는 꽤 괜찮았다.그런데 세 번째, 네 번째 패턴을 생각하기 시작하면서 이상한 점이 보였다.AI 게임을 만들고 있는데, 내가 모든 답을 만들고 있었다플레이어가 공격 후 투척을 자주 한다면 그에 맞는 스킬을 만든다.대시 후 공격을 자주 한다면 또 다른 스킬을 만든다.여러 적 사이를 오가며 싸운다면 또 하나를 만든다.몇 개 정도는 충분히 할 수 있다.문제는 계속 늘어날 때다.행동이 늘고,물체가 늘고,대상이 늘고,시간 조건이 늘고,공간 관계까지 들어오기 시작하면 가능한 조합도 빠르게 많아진다.그리고 그때마다 내가“이 패턴에는 이 스킬.”이라고 완성된 답을 하나씩 추가하고 있었다.생각해보니 조금 이상했다.AI가 플레이어를 읽고 새로운 능력을 만들어주는 게임을 만들겠다고 시작했는데,정작 가능한 플레이 스타일과 그 결과는 내가 전부 미리 만들고 있었다.이 구조라면 나중에 AI가 들어오더라도 결국 내가 만들어둔 답안 중 하나를 고르는 역할밖에 하지 못한다.그래서 완성된 스킬을 계속 추가하는 대신,스킬 자체를 표현할 수 있는 작은 언어를 만들어보기로 했다.스킬 대신 ‘스킬을 만드는 문법’을 만든다하나의 능력을 잘게 나눠보면 생각보다 몇 가지 질문으로 정리할 수 있었다.무슨 행동에서 시작되는가? ↓ 무엇을 기억하는가? ↓ 무엇과 연결하는가? ↓ 어디에 적용하는가? ↓ 어떤 효과가 생기는가?예를 들어,“물체를 던져 맞힌 충격을 기억했다가 다음에 집은 물체의 공격으로 전달한다”라는 능력도 이렇게 쪼개볼 수 있다.투척 적중 ↓ 이전 충격 기억 ↓ 다음 물체와 연결 ↓ 다음 성공 공격까지 유지 ↓ 기억한 힘을 전달다른 능력은 같은 재료를 전혀 다르게 사용할 수 있다.대시 ↓ 대시 시작 위치 기억 ↓ 다음 근접 공격 위치와 연결 ↓ 두 위치 사이 ↓ 새로운 효과 발생또는적 A 공격 ↓ 적 B 공격 ↓ 다시 적 A 공격 ↓ 기억한 적 B의 위치 ↓ 효과 발생각각만 보면 전혀 다른 스킬처럼 보인다.하지만 내부적으로는행동을 감지하고, 무언가를 기억하고, 다른 대상이나 위치와 연결하고, 그 결과를 새로운 효과로 바꾼다는 비슷한 언어로 표현할 수 있다.내가 원했던 건 바로 이쪽이었다.완성된 스킬을 계속 만드는 대신, 여러 스킬을 표현할 수 있는 언어를 만드는 것.그리고 언젠가 AI가 스킬을 만든다면,아무것도 없는 곳에서 게임 규칙을 마음대로 발명하는 게 아니라 이 언어 안에서 의미 있는 조합을 찾게 하고 싶었다.그렇다고 AI에게 모든 걸 자유롭게 만들게 할 수는 없었다처음부터 LLM에게 이렇게 요청할 수도 있다.“이 플레이어에게 어울리는 재미있는 스킬 하나 만들어줘.”아마 그럴듯한 아이디어는 꽤 잘 나올 것이다.하지만 아이디어와 실제 게임 규칙은 다르다.모델이 존재하지 않는 기능을 사용할 수도 있다.앞에서 기억하지 않은 대상을 뒤에서 참조할 수도 있다.서로 연결할 수 없는 두 효과를 붙일 수도 있다.A가 B를 부르고, B가 다시 A를 불러서 효과가 끝없이 반복될 수도 있다.무언가를 기억하게 해놓고 언제 그 기억을 지워야 하는지 정의하지 않을 수도 있다.게임에서는 이런 문제가 단순한 문장 오류로 끝나지 않는다.잘못하면 실제 전투 상태가 망가지거나, 한 번의 행동에서 끝없이 효과가 발생할 수도 있다.그래서 한 가지 경계를 만들었다.AI가 무언가를 제안할 수는 있지만, 그 제안이 곧 게임의 규칙이 되지는 않는다.조합할 수 있다는 것과, 실행할 수 있다는 것은 달랐다그래서 스킬 문법 뒤에 Compiler를 두었다.스킬 조합이 들어오면 바로 실행하지 않고 먼저 검사한다.실제로 존재하는 기능들로 이루어져 있는지.앞 단계에서 나온 결과를 다음 단계가 받을 수 있는지.기억해야 하는 상태가 있다면 언제 생성되고 언제 사라지는지.효과가 자기 자신을 다시 부르는 구조는 없는지.한 번의 행동에서 너무 많은 효과를 만들어내지는 않는지.그리고 마지막으로,실제로 플레이어에게 설명할 수 있는 능력인지까지 확인한다.개념적으로는 이런 흐름에 가깝다.Skill 조합 ↓ 게임 규칙과 맞는가? ↓ 상태와 연결이 유효한가? ↓ 끝없이 반복되지 않는가? ↓ 실제로 실행 가능한가? ↓ 플레이어에게 설명 가능한가? ↓ 게임에 적용이 과정을 만들면서 생각보다 중요했던 게 하나 더 있었다.게임에서 실제로 일어나는 것과 플레이어가 읽는 설명이 서로 달라지면 안 된다는 점이다.예를 들어 실제 효과는 3초 동안 유지되는데 스킬 설명에는 4초라고 적혀 있다면, Adaptive System을 플레이어가 믿기 어렵다.그래서 지금은 실제 능력과 UI 설명이 따로 존재하지 않는다.같은 검증된 결과에서 실제 효과도 만들고, 플레이어가 읽는 설명도 만든다.스킬 이름이나 설명 역시 LLM이 즉석에서 자유롭게 작성하도록 하지 않았다.게임의 문체와 실제 동작을 사람이 통제할 수 있도록 미리 만들어둔 표현을 사용한다.결국 사람이 만드는 것과 AI가 고르는 것을 나누게 됐다여기까지 만들고 나니 AI에게 어디까지 맡기고 싶은지도 조금 명확해졌다.사람이 해야 하는 일은 게임에서 가능한 것의 경계를 만드는 것이다.어떤 행동이 존재하는지.무엇을 기억할 수 있는지.어떤 관계가 가능한지.어떤 효과를 실제 게임에서 지원하는지.그리고 그 조합이 안전한지.반대로 AI에게 기대했던 건,그 가능한 조합들 중 지금 이 플레이어의 행동과 의미 있게 연결되는 것을 찾는 일이었다.즉 내가 만들고 싶었던 건 AI가 게임의 법칙을 마음대로 쓰는 구조가 아니었다.사람이 게임의 언어를 만들고,AI는 그 언어 안에서 플레이어에게 맞는 문장을 찾는다.이쪽에 더 가까웠다.이제야 Local LLM을 시험할 준비가 됐다여기까지 오고 나서야 처음 생각했던 실험을 제대로 해볼 수 있게 됐다.플레이어가 무엇을 반복하고 있는지는 게임이 계산한다.그 행동을 능력으로 표현할 수 있는 문법도 있다.잘못된 조합을 막을 수 있는 Compiler도 있다.실제 효과와 UI 설명도 같은 결과에서 나온다.즉 모델이 실수하더라도 게임 자체가 무너지지 않도록 필요한 경계는 어느 정도 만들어졌다.그리고 이제 남은 질문은 하나였다.이 조합 문제를 정말 Local LLM이 규칙 기반 방식보다 더 잘 풀 수 있을까?다음 글에서는 실제 Local LLM을 게임에 넣고,처음에는 완성된 능력 중 하나를 고르게 해보고,그다음에는 능력 자체를 조합하게 해보고,마지막에는 내부적인 복잡함까지 최대한 걷어낸 뒤 다시 시험해본 이야기를 적어보려고 한다.결과는 처음 기대했던 방향과는 꽤 달랐다.

Blog thumbnail
정이현

정이현님이 쇼케이스를 끌어올렸어요· 1일 전

Showcase thumbnail

아스믈

원하는 ASMR을 겹쳐서 생산성을 올려보세요

mustardoo

mustardoo님이 블로그 글을 발행했어요· 1일 전

Dash를 30번 했다고 ‘Dash형 플레이어’일까? — UNSPECIFIED #2

지난 글에서는 플레이어가 싸우는 방식을 게임이 읽고, 그 행동을 새로운 능력으로 되돌려주는 게임을 만들고 있다고 이야기했다.그런데 막상 구현을 시작하니 생각보다 먼저 답해야 하는 질문이 있었다.무엇을 플레이어의 ‘습관’이라고 부를 수 있을까?처음에는 간단할 줄 알았다.공격을 몇 번 했는지,대시를 몇 번 했는지,물체를 몇 번 던졌는지 기록하면 어느 정도 플레이 스타일이 보일 것 같았다.하지만 실제 전투에 붙여보니 그렇지 않았다.Dash를 30번 했다는 건 별로 많은 걸 말해주지 않는다한 플레이어가 전투 중 Dash를 30번 사용했다고 해보자.그러면 이 사람을 ‘Dash를 적극적으로 사용하는 플레이어’라고 볼 수 있을까?꼭 그렇지는 않다.단순히 이동할 때 Dash를 많이 사용했을 수도 있고, 적의 공격을 피할 때만 사용했을 수도 있다.반대로 Dash는 10번밖에 사용하지 않았지만 매번 이런 식으로 싸웠을 수도 있다.공격 → 대시 → 다시 공격이쪽이 오히려 훨씬 뚜렷한 습관처럼 보인다.같은 대시라도적의 공격 → 대시로 회피 → 반격과는 의미가 전혀 다르다.결국 알고 싶었던 건 버튼을 얼마나 많이 눌렀느냐가 아니었다.어떤 행동을, 어떤 상황에서, 무엇과 연결해서 반복했는가.그래서 UNSPECIFIED에서는 단순한 행동 횟수보다 행동 사이의 관계를 보기 시작했다.공격하다가 같은 물체를 던지는지.물체를 던진 뒤 곧바로 다른 물체를 집는지.대시한 뒤 공격을 이어가는지.한 적을 공격한 뒤 별개의 공격으로 다른 적을 노리는지.이렇게 반복해서 나타나는 작은 행동 패턴을 프로젝트에서는 Motif라고 부르고 있다.그런데 ‘게임에서 일어난 일’과 ‘플레이어가 한 일’은 달랐다행동을 기록하기 시작하니 또 다른 문제가 생겼다.예를 들어 폭발하는 물체를 하나 던졌다고 해보자.폭발에 적 세 명이 맞았다.게임 입장에서는 세 번의 Hit가 발생했다.그렇다면 플레이어는 공격을 세 번 한 걸까?당연히 아니다.플레이어가 직접 한 행동은 물체를 한 번 던진 것이다.세 번의 Hit는 그 행동으로 인해 발생한 결과다.이 구분을 하지 않으면 게임에 연쇄 효과가 많아질수록 기록이 이상해진다.플레이어는 버튼을 한 번 눌렀는데, 시스템은 그 사람을 갑자기 여러 번 공격한 플레이어처럼 이해할 수 있다.그래서 구현하면서 행동을 크게 나눠서 보기 시작했다.플레이어가 직접 선택한 행동그 행동으로 발생한 결과그 결과에서 다시 파생된 효과한 번의 투척에서 폭발이 발생하고, 그 폭발이 여러 적을 맞히더라도 원래 시작점은 하나다.반대로 플레이어가 튕겨 돌아온 물체를 직접 받아 다시 던졌다면, 그건 새로운 선택이므로 새로운 행동이 된다.게임에서 일어난 사건과 플레이어가 선택한 행동은 같지 않았다.이 문장을 구분하지 못하면 이후의 분석도 전부 흔들렸다.버튼을 눌렀다고 행동한 것도 아니다반대의 경우도 있었다.플레이어가 버튼을 눌렀다고 해서 항상 행동이 발생하는 건 아니다.대시가 아직 쿨다운인데 대시 버튼을 눌렀을 수도 있다.주변에 아무 물체도 없는데 줍기 버튼을 눌렀을 수도 있다.공격할 수 없는 상태에서 공격을 시도했을 수도 있다.이걸 전부 플레이어의 행동으로 기록하면 실제 플레이와 기록 사이에 차이가 생긴다.그래서 현재는 입력 자체를 행동으로 취급하지 않는다.전투 시스템에서 실제로 승인되고, 게임 안에서 확정된 행동만 기록한다.이 부분은 처음에는 꽤 사소하게 느껴졌다.그런데 나중에 이 데이터를 AI가 읽게 된다고 생각하니 오히려 중요해졌다.AI가 이상한 결론을 내렸을 때 흔히 모델부터 의심하기 쉽다.하지만 모델이 아무리 좋아도 입력 데이터가 플레이어 행동을 잘못 표현하고 있다면 제대로 된 결론을 내릴 수 없다.AI에게 플레이어를 이해시키기 전에, 게임부터 플레이어에 대해 거짓말하지 않아야 했다.1번 중 1번과 10번 중 8번행동 관계를 찾기 시작한 뒤에도 문제가 하나 더 있었다.예를 들어 두 플레이어가 있다고 해보자.플레이어 A 1번의 기회 중 1번 성공 → 100% 플레이어 B 10번의 기회 중 8번 성공 → 80%비율만 보면 A가 더 높다.하지만 둘 중 어느 쪽을 ‘습관’이라고 부르기 쉬울까?아마 B에 가깝다.한 번의 우연한 행동과, 여러 번 기회가 있었는데 계속 같은 선택을 한 것은 다르기 때문이다.그래서 Motif에는 성공 횟수만 쌓지 않는다.그 행동을 할 수 있었던 기회가 몇 번 있었는지도 함께 본다.예를 들어 ‘투척 후 다른 물체를 집는다’는 패턴이라면, 투척 자체가 새로운 기회가 된다.그 뒤 일정 시간 안에 물체를 다시 획득하면 성공으로 본다.시간이 너무 오래 지나면 같은 행동 관계로 보지 않는다.즉 단순히Throw 20회 Pickup 15회를 보는 게 아니라,Throw ↓ 일정 시간 안 Pickup이라는 관계가 실제로 얼마나 반복됐는지를 보는 식이다.이렇게 해야 우연히 한두 번 나온 행동과 반복된 습관을 조금이나마 구분할 수 있었다.여기까지는 LLM이 아무것도 하지 않는다이 부분은 프로젝트를 만들면서 꽤 중요하게 정한 부분이다.현재 Motif를 계산하는 과정에는 LLM이 들어가지 않는다.전투 로그 전체를 모델에게 넘긴 뒤“이 플레이어의 플레이 스타일을 분석해줘.”라고 하지 않는다.어떤 행동이 실제로 일어났는지,어떤 행동과 어떤 행동이 이어졌는지,그 관계를 시도할 기회가 몇 번 있었고 몇 번 반복됐는지.이런 사실은 게임 코드가 먼저 계산한다.대략적인 흐름은 이렇다.실제 전투 ↓ 확정된 행동과 결과 ↓ 반복되는 행동 관계 ↓ MotifLLM이 들어갈 자리는 그 이후다.사실을 만드는 것과, 그 사실을 해석하는 일을 분리하고 싶었다.그래서 ‘습관’은 버튼 횟수가 아니게 됐다지금 단계에서 내가 습관이라고 부르는 건 단순히 많이 사용한 행동이 아니다.같은 상황에서 반복해서 나타나는 행동 관계에 조금 더 가깝다.물체를 들면 자주 공격한 뒤 던지는 사람.던지고 나면 바로 새로운 물체를 찾는 사람.대시를 회피보다 공격 연결에 자주 사용하는 사람.한 적에게 오래 머무르기보다 여러 적 사이를 옮겨 다니는 사람.이런 차이가 쌓이면 같은 게임을 해도 서로 다른 플레이 기록이 만들어진다.그리고 여기서부터 처음 만들고 싶었던 문제에 조금 가까워졌다.게임은 이제 어느 정도“이 플레이어가 이런 행동을 반복하고 있다.”라고 말할 수 있게 됐다.하지만 여기까지만 만들면 결국 플레이 스타일 분석기다.내가 만들고 싶은 건 분석 결과를 보여주는 게임이 아니었다.게임이 플레이어의 행동을 알아챘다면, 그 사실이 실제 전투에도 영향을 줘야 한다.그래서 다음 질문은 자연스럽게 정해졌다.이 행동을 어떻게 실제로 사용할 수 있는 스킬로 바꿀 것인가?다음 글에서는 AI에게 스킬을 만들게 하려다가, 오히려 먼저 게임이 스킬을 표현할 수 있는 ‘언어’부터 만들게 된 이야기를 적어보려고 한다.

Blog thumbnail
Frony

Frony님이 쇼케이스를 등록했어요· 1일 전

Showcase thumbnail

Tronia (트로니아)

2030 전용 해외 패키지를 여행사 구분 없이 출발일, 가격, 남은 자리로 비교하는 사이트

주차의민족

주차의민족님이 쇼케이스를 등록했어요· 1일 전

Showcase thumbnail

주차의 민족

목적지를 검색하고 주차장을 추천받는 서비스, 주차의 민족입니다!

장현

장현님이 쇼케이스를 끌어올렸어요· 1일 전

Showcase thumbnail

schoolecho

급식과 시간표 확인부터 영원히 남는 우리 반 이야기까지, 학생 필수 올인원 플랫폼 schoolecho

리이오

리이오님이 블로그 글을 발행했어요· 1일 전

연주를 멈추지 않는 메트로놈, 낫마템포

"faster" 한마디면 템포가 올라갑니다. 손은 악기에서 떨어지지 않습니다.App Store에서 낫마템포 받기양손이 악기에 묶여 있을 때기타를 메고, 왼손은 코드를 잡고, 오른손엔 피크를 쥐고 있습니다. 120으로는 너무 느리다는 느낌이 옵니다. 이제 둘 중 하나를 골라야 합니다. 연주를 멈추고 화면을 만지거나, 어색한 템포로 그냥 버티거나.낫마템포(Not My Tempo) 는 이 순간을 위해 만든 메트로놈입니다. 연주를 멈추지 않고 그냥 말하면 됩니다. "faster". 그러면 템포가 5 올라갑니다. 손은 악기에서 떨어지지 않습니다.문제는 메트로놈이 아니라 손이었다앱스토어에 메트로놈은 넘칩니다. 정확한 박자, 예쁜 디자인, 수많은 프리셋. 그런데 거의 모두가 같은 전제를 깔고 있습니다. 사용자의 손이 비어 있다는 전제입니다.연습하는 사람의 손은 비어 있지 않습니다. 피아니스트는 건반 위에, 드러머는 스틱을 들고, 관악기 연주자는 악기를 받치고 있습니다. 그래서 연습은 이렇게 끊깁니다.템포를 바꾸려면 연주를 멈춘다.악기를 내려놓거나 손을 떼서 화면을 누른다.다시 자세를 잡고, 아까 그 느낌을 찾아 헤맨다.하루에 수십 번 반복되는 이 작은 단절이 집중을 갉아먹습니다. 낫마템포가 정의한 문제는 한 줄입니다.메트로놈은 연주자의 손을 빌리지 말아야 한다.한 가지 더 있습니다. iOS에도 음성 제어 기능이 있지만, 설정 깊숙이 숨어 있고 켜는 순간 폰 전체의 동작이 바뀝니다. 연습하려고 접근성 설정을 뒤지는 사람은 없습니다.우아한 해결 1. 앱이 스스로 듣는다낫마템포는 iOS 음성 제어나 어떤 접근성 설정도 요구하지 않습니다. 앱이 자기 마이크로 직접 듣습니다. Listen 버튼을 한 번 누르면 끝입니다.말하면일어나는 일"start" / "stop"재생 / 정지"faster" / "slower"템포 ±5"up" / "down"템포 ±1"tempo 120" 또는 그냥 "120"그 템포로 바로 설정"double" / "half"템포 두 배 / 절반"eighth" / "triplet" / "sixteenth"박 분할 변경"three beats"3/4 박자로"tune"튜너 열기"close"열린 창 닫기여기까지는 아이디어입니다. 진짜 이야기는 이걸 연습 내내 끊기지 않게 만드는 데 있었습니다.노래 한 곡보다 오래 듣는다. 애플의 음성 인식은 한 번에 약 1분까지만 듣습니다. 연주 중에 조용히 귀가 닫히면 사용자는 앱이 고장났다고 느낍니다. 낫마템포는 그 한계에 닿기 전에 인식만 새것으로 갈아 끼웁니다. 마이크와 오디오 엔진은 그대로 둔 채 가벼운 인식 요청만 교체하기 때문에, 메트로놈 소리와 마이크가 서로를 방해하지 않습니다.한 번 말하면 한 번만. "faster"라고 말하면 인식기는 그 단어를 문장 안에 계속 들고 있습니다. 그대로 두면 템포가 5, 10, 15씩 계속 올라가겠죠. 명령이 실행되는 순간 그 발화를 닫고 새 귀로 다시 듣기 시작합니다. 한 마디는 정확히 한 번 실행됩니다.헷갈리는 말을 먼저 걸러낸다. "speed up" 같은 긴 표현은 "up"보다 먼저 검사하고, 박자 변경은 반드시 "beat"가 들어가야 동작합니다. 숫자 "3"이 템포 3인지 3박자인지 앱이 헷갈리지 않도록요. "done"은 "down"으로 잘못 들리기 쉬워서 "close" 같은 또렷한 대안도 함께 받습니다.목소리는 폰 밖으로 나가지 않는다. 기기에 영어 음성 모델이 있으면 온디바이스로만 인식합니다. 연습실에서 하는 혼잣말이 서버로 가지 않습니다.배터리도 생각한다. 3분 동안 아무 명령이 없으면 마이크를 스스로 쉬게 합니다. 다시 Listen을 누르면 돌아옵니다.우아한 해결 2. 들리는 클릭, 정확한 클릭메트로놈의 본업은 소리입니다. 그런데 마이크를 켜 둔 앱은 소리가 작아지기 쉽습니다. iOS는 녹음 중인 앱의 출력을 조용한 경로로 보내고, 때로는 스피커 대신 통화용 수화기로 소리를 돌려 버립니다. 볼륨을 끝까지 올려도 클릭이 안 들리는 이유입니다.낫마템포는 오디오 세션을 한 곳에서 관리합니다. 평소에는 가장 큰 재생 모드에 있다가, 마이크가 실제로 열려 있는 동안에만 녹음 모드를 빌려 씁니다. 그 사이 소리가 수화기로 새면 스피커로 되돌립니다. 이어폰이나 블루투스를 쓰는 중이라면 건드리지 않습니다.클릭 자체도 다시 설계했습니다. 오디오 파일 없이 코드로 직접 만든 소리입니다.배음을 더했습니다. 순수한 사인파는 최고점은 높아도 귀에는 가늘게 들립니다. 기음에 배음 두 개를 얹어 방 안의 소음을 뚫고 나오게 했습니다.부드럽게 포화시키고 정규화했습니다. 찢어지는 소리 없이 체감 음량을 끌어올렸습니다.1ms 페이드인과 5ms 페이드아웃. 소리가 시작하고 끝나는 순간의 '틱' 하는 잡음을 없앴습니다.50ms 안에 끝납니다. 260BPM의 16분음표 사이에도 들어가는 길이입니다.다운비트, 박, 분할 클릭은 각각 다른 음높이로 울립니다. 16분음표 연습 중에도 지금 어디가 박인지 귀가 놓치지 않습니다.우아한 해결 3. 눈으로 읽는 박자연주자는 메트로놈 화면을 정면으로 보지 않습니다. 악보나 지판을 보면서 곁눈으로 봅니다. 그래서 시각화도 주변시를 기준으로 만들었습니다.화면 전체가 박에 맞춰 숨 쉽니다. 매 박마다 배경이 아주 옅게 번쩍입니다. 다운비트는 붉게, 나머지 박은 은은한 금빛으로요. 화면을 보고 있지 않아도 시야 가장자리에서 박이 느껴집니다.그 위에 네 가지 보기 방식이 있습니다. "switch view"라고 말하면 차례로 바뀝니다.보기이렇게 움직입니다진자공이 중력처럼 움직입니다. 클릭 순간 가운데 점선 표적을 가장 빠르게 통과하며 터지고, 끝에서는 잠시 떠 있다 돌아옵니다.궤도원을 따라 도는 점. 재생 중 링을 탭하면 내 타이밍이 Perfect, Good, Bad 중 무엇인지 바로 알려 줍니다.링 스윕한 박마다 링이 12시부터 차오르고, 가운데에 지금 박 번호가 크게 뜹니다.다각형마디 자체가 도형이 됩니다. 5/4는 오각형, 7/4는 칠각형. 16분음표로 쪼개면 오각형이 20각형 안에 들어갑니다.다각형 보기는 변박 연습을 위해 만들었습니다. 점 일곱 개가 한 줄로 늘어서면 지금 몇 번째인지 세어야 하지만, 칠각형의 꼭짓점은 한눈에 위치가 읽힙니다.궤도 보기의 타이밍 체크는 놓친 박을 잔상으로 남깁니다. 내가 탭한 순간 점이 어디 있었는지가 그대로 그려져서, 마커와 잔상 사이의 거리가 곧 나의 오차가 됩니다. 판정 횟수는 글자 없이 초록, 금빛, 빨간 원 안의 숫자로만 쌓입니다.우아한 해결 4. 진지한 연습을 위한 도구스피드 트레이너. 정해 둔 마디 수마다 템포가 자동으로 올라가고, 목표 템포에 닿으면 멈춥니다. 어려운 구간을 조금씩 빠르게 반복하는 연습을 손 하나 대지 않고 할 수 있습니다.악센트 에디터. 분할 카드를 두 번 탭하면 마디 전체가 칸으로 펼쳐집니다. 각 칸은 탭할 때마다 높은 클릭, 낮은 클릭, 쉼으로 바뀝니다. 4/4 16분음표라면 16칸을 하나하나 내 리듬으로 만들 수 있고, 곡마다 이름을 붙여 저장해 둘 수 있습니다.튜너. "tune"이라고 말하면 메인 화면이 옆으로 밀리고 튜너가 들어옵니다. 음성 인식이 쓰는 마이크 입력을 그대로 나눠 쓰기 때문에, 오디오 엔진을 하나 더 띄우지 않습니다.디테일. 모두를 위한 메트로놈손을 쓰지 않는 메트로놈은 손을 쓰기 어려운 사람에게도 메트로놈이 됩니다. 그래서 접근성은 덤이 아니라 같은 문제의 다른 얼굴로 다뤘습니다.VoiceOver 사용자를 위해 첫 실행 때 음성 안내를 들려주고, 모든 기능을 커스텀 액션으로 열어 두었습니다.보조 접근(Assistive Access) 모드에서는 템포, 느리게와 빠르게, 시작과 정지만 남긴 큰 화면으로 바뀝니다.iOS 음성 제어를 이미 쓰는 사용자라면 앱의 마이크가 물러나고, 버튼 이름으로 조작하도록 자리를 내어 줍니다.다크 모드, 다이내믹 타입, 가로 모드, 동작 줄이기 설정을 모두 따릅니다.보이지 않는 곳도 다듬었습니다. 전화나 Siri가 오디오를 가져가면 깨끗하게 멈추고, 이어폰을 꽂거나 뺄 때 오디오 경로가 바뀌어도 소리 없이 '재생 중'인 상태로 남지 않고 스스로 다시 연결합니다. 재생 중에 악센트를 편집해도, 빠르게 시작과 정지를 연타해도 앱이 꺼지지 않도록 하나하나 막아 두었습니다. 연습 중에 꺼지는 메트로놈만큼 맥 빠지는 것은 없으니까요.손은 악기에, 템포는 목소리로낫마템포가 하는 일은 결국 하나입니다. 연습과 메트로놈 사이에 끼어 있던 손가락 한 번을 지우는 것. 그 작은 단절이 사라지면 연습은 생각보다 훨씬 오래, 깊게 이어집니다.메트로놈, 음성 제어, 접근성 기능은 모두 무료입니다. 스피드 트레이너, 악센트 에디터와 프리셋, 튜너는 Pro에서 쓸 수 있습니다.오늘 연습할 때는 화면 대신 이렇게 말해 보세요. "start".App Store에서 낫마템포 받기

Blog thumbnail
리이오

리이오님이 쇼케이스를 끌어올렸어요· 1일 전

Showcase thumbnail

낫 마 템포

손 대지 않고 사용하는 메트로놈

하루들

하루들님이 쇼케이스를 끌어올렸어요· 1일 전

Showcase thumbnail

하루들

우리끼리 통하는 네컷만화

Mapmory

Mapmory님이 쇼케이스를 등록했어요· 1일 전

Showcase thumbnail

Mapmory

여행 사진과 장소를 연결해, 다시 꺼내볼 수 있는 기억 지도를 만듭니다.

mustardoo

mustardoo님이 블로그 글을 발행했어요· 2일 전

AI가 내 플레이를 보고 스킬을 만든다면 — UNSPECIFIED #1

최근 새로운 게임 프로젝트를 시작했다.이름은 UNSPECIFIED.출발점은 꽤 단순한 질문이었다.게임 속 AI가 플레이어의 싸우는 방식을 보고, 그걸 실제 능력으로 만들어줄 수 있을까?AI NPC와 자유롭게 대화하는 게임?내가 궁금했던 건 조금 다른 쪽이었다.같은 액션게임을 해도 사람마다 싸우는 방식은 꽤 다르다.누군가는 적과 계속 붙어서 싸우고, 누군가는 거리를 유지한다.대시 하나만 해도 어떤 사람은 회피할 때만 쓰고, 어떤 사람은 공격과 공격 사이를 연결하는 용도로 쓴다.이런 차이를 게임이 알아챌 수 있다면 어떨까.그리고 단순히 분석 결과를 보여주는 데서 끝나는 게 아니라,“네가 계속 이렇게 싸우고 있으니, 그 행동 자체를 새로운 능력으로 바꿔볼게.”라고 할 수 있다면 꽤 재미있겠다고 생각했다.가능하다면 이 판단도 외부 API가 아니라 게임 안에서 돌아가는 Local LLM으로 해보고 싶었다.그게 프로젝트의 시작이었다.그런데 AI가 읽을 행동부터 있어야 했다처음부터 모델을 붙일 수도 있었다.하지만 금방 한 가지 문제가 보였다.플레이어가 할 수 있는 행동이 단순하면, AI가 분석할 것도 단순하다.공격과 대시밖에 없다면 아무리 모델이 좋아도 결국 “공격을 많이 했다”, “대시를 많이 했다” 정도에서 크게 벗어나기 어렵다.그래서 먼저 사람마다 다르게 싸울 수 있는 액션게임의 바닥을 만드는 쪽으로 갔다.UNSPECIFIED의 기본 전투는 주변의 물체를 즉석에서 무기로 사용하는 구조다.전투의 기본 흐름을 한 줄로 쓰면 이렇다.집는다 → 공격한다 → 대시한다 → 던진다 → 다른 것을 집는다처음에는 이동, 일반공격, 강공격, 대시부터 만들었다.공격 중 언제 대시로 끊을 수 있는지, 대시와 피격이 거의 동시에 들어오면 어떤 결과가 나오는지, 서로의 공격이 동시에 맞으면 어떻게 처리할지 같은 경계부터 하나씩 정리했다.액션게임에서는 이런 부분이 조금만 흔들려도 바로 이런 느낌이 생긴다.“분명 눌렀는데 왜 맞았지?”그래서 충돌이 감지됐다고 곧바로 피해를 주는 식으로 만들지도 않았다.먼저 공격이 닿을 수 있는 후보를 모으고, 그 순간의 대시·공격·피격 상태를 기준으로 결과를 확정한 뒤 한 번에 반영하는 식으로 구성했다.처음에는 그냥 액션게임을 안정적으로 만들기 위한 선택이었다.그런데 나중에 보니 이게 꽤 중요했다.게임이 플레이어를 관찰하려면, 게임 스스로 먼저 ‘실제로 무슨 일이 일어났는지’를 정확히 알고 있어야 했다.무기가 아니라, ‘들고 있는 물체’그다음에는 무기를 추가했다.정확히는 전통적인 무기 슬롯을 만든 게 아니라, 월드의 물체를 들고 있는 상태로 다루는 구조를 만들었다.빈손으로 물체를 집고, 다시 버튼을 누르면 조준하고, 버튼을 놓는 순간 던진다.현재는 성격이 서로 다른 몇 가지 물체를 시험하고 있다.파이프는 기본적인 근접 공격과 투척의 기준점이다.의자는 사용하다 보면 부서진다. 마지막 공격이 정상적으로 적중한 뒤에 부서지기 때문에, 공격 도중 무기가 사라지면서 판정까지 취소되는 식으로 동작하지 않는다. 폭발성 물체는 부서지는 순간 범위 효과를 만든다. 기어 형태의 물체는 벽에 튕기고, 한 번 튕긴 뒤에는 나에게도 위험해진다. 대신 타이밍을 맞추면 다시 받아서 바로 던질 수도 있다.여기서 중요했던 건 공격력과 사거리만 다른 무기를 여러 개 만드는 게 아니었다.물체 하나가 추가될 때마다 플레이어의 행동도 달라져야 했다.계속 들고 싸울지, 빨리 던질지, 부서질 때까지 사용할지, 다른 물체로 갈아탈지, 튕겨 돌아오는 위험까지 활용할지.이런 선택이 생겨야 나중에 AI도 단순히“이 플레이어는 이 무기를 좋아한다.”가 아니라,“이 플레이어는 어떤 행동을 어떤 식으로 연결해서 반복하는가?”를 볼 수 있다.여기서부터 원래 만들고 싶었던 문제가 시작됐다기본 전투와 무기를 활용한 전투가 만들어지고 나니 드디어 처음 질문으로 돌아올 수 있었다.예를 들어 한 플레이어가 대시를 30번 했다고 하자.그 사실만으로 이 사람이 대시 중심으로 싸운다고 말할 수 있을까?아마 아닐 것이다.그냥 이동할 때 많이 썼을 수도 있다.반대로 대시는 10번밖에 하지 않았지만, 매번 공격 직후 대시하고 다시 다른 공격으로 이어갔다면 오히려 더 뚜렷한 습관일 수 있다.결국 보고 싶었던 건 버튼 사용량이 아니었다.행동과 행동 사이의 관계였다.그래서 다음에는 플레이어가 무엇을 했는지를 기록하는 것에서 한 단계 더 나아가,그중 무엇을 ‘습관’이라고 볼 것인가를 만들기 시작했다.

Blog thumbnail