> ## Content Index
> Fetch the complete content index at: https://blog.sorune.org/llms.txt
> Use this file to discover other available public pages before exploring further.

# 캐릭터를 움직이는 것과 살아 있게 만드는 것은 다르다 — CharaWeave Character Core 설계
- URL: https://blog.sorune.org/charaweave-character-core-boundary/
- Published: 2026-09-11T07:22:56.000Z
- Updated: 2026-09-15T01:10:20.000Z
- Description: CharaWeave에서 Runtime Foundation과 Character System을 분리한 이유를 정리한다. 움직임, 표현, 성격, 욕구, 기억을 하나의 상태 머신으로 섞지 않고 의미의 소유권을 나누는 설계 이야기다.
- Author: Sorune — Engineering, Photography & Lab Notes
- Tags: Projects, CharaWeave, Character Systems, Architecture, TypeScript, Rive

화면 위에서 캐릭터가 걷고, 드래그되고, 다른 영역으로 이동하면 꽤 그럴듯해 보인다. 하지만 움직인다는 사실만으로 캐릭터가 “살아 있다”고 느껴지지는 않는다.

CharaWeave를 만들면서 점점 명확해진 경계가 하나 있었다.

```text
움직임을 실행하는 시스템
!=
왜 움직였는지를 결정하는 시스템

```

초기 Runtime은 lifecycle, Host, Geometry, Motion, Drag, Recovery, Presentation 같은 실행 기반을 다룬다. 이 계층은 캐릭터를 안전하게 화면에 존재하게 하고, 어떤 Intent를 실제 동작으로 바꾸는 데 집중한다.

하지만 지속적인 캐릭터 정체성은 다른 문제다.

“지루해서 새로운 곳을 찾는다”, “낯선 대상을 더 궁금해한다”, “같은 대상을 반복해서 방문하지 않는다”, “어떤 경험 이후 다음 선택이 달라진다” 같은 것은 단순한 이동 알고리즘이 아니다. 이런 의미를 Runtime에 밀어 넣기 시작하면 실행 상태와 캐릭터 상태가 빠르게 뒤섞인다.

그래서 Character System은 Runtime Foundation 위에 별도의 의미 계층으로 설계했다.

## Runtime과 Character를 분리한다

현재 CharaWeave의 큰 구조는 다음처럼 생각하고 있다.

```text
Life State
    ↓
Motivation / Needs
    ↕
Personality
    ↕
Memory / Affinity / Habit
    ↓
WHEN / WHAT / TARGET
    ↓
Intent
    ↓
Runtime realization
    ↓
Result
    ↓
Life / Memory update
    ↺

```

여기서 중요한 부분은 화살표보다 **소유권**이다.

Runtime은 “어떻게 실행할 것인가”를 안다. Character Core는 “왜 이 행동이 지금 의미가 있는가”를 안다. Presentation은 “그 의미를 어떻게 보여줄 것인가”를 안다.

이 경계를 유지하면 같은 캐릭터 의미를 다른 renderer나 presentation backend에서도 재사용할 수 있다.

## 걷고 있다는 사실은 Life State가 아니다

상태를 설계할 때 가장 쉽게 생기는 문제는 관찰 가능한 모든 것을 하나의 거대한 Character State에 넣는 것이다.

예를 들어 다음은 실제로 상태처럼 보인다.

- walking
- dragging
- recovering
- current target
- current Intent
- browser lifecycle

하지만 이 값들은 Character의 장기적인 내적 상태라기보다 Runtime execution state에 가깝다.

반대로 boredom, energy, affinity처럼 다음 행동의 의미에 영향을 주는 값은 Character-owned state가 될 수 있다.

이 차이를 무시하면 “캐릭터가 걷고 있으니 활력이 증가한다” 같은 규칙과 “브라우저 resize 때문에 이동이 취소됐다” 같은 실행 이벤트가 동일한 상태 머신에서 경쟁하게 된다. 구현은 가능하지만 수정할수록 이유를 설명하기 어려워진다.

## 첫 번째 목표는 화려한 행동이 아니라 닫힌 루프였다

Character Core의 첫 vertical slice에서 일부러 새로운 행동 종류를 먼저 만들지 않는 방향을 택했다.

대신 이미 Runtime이 수행할 수 있는 방문 행동을 재사용하면서 다음 루프를 확인하는 것이 우선이었다.

```text
boredom 증가
    ↓
explore motivation
    ↓
Personality의 curiosity가 기여
    ↓
WHEN / WHAT / TARGET 결정
    ↓
기존 visit 동작 실행
    ↓
Result
    ↓
boredom 변화
    ↺

```

핵심은 캐릭터가 단순히 목적지로 이동했다는 것이 아니다.

**Character state가 행동의 이유를 만들고, 행동 결과가 다시 Character state를 바꾸는가?**

이 피드백 루프가 닫히면 이후 행동을 추가해도 같은 구조 위에 쌓을 수 있다. 반대로 루프 없이 행동 vocabulary부터 늘리면 애니메이션과 action enum은 많아지지만 Character System의 중심은 여전히 비어 있을 수 있다.

## Personality는 행동을 직접 명령하지 않는다

Personality도 비슷한 경계가 필요했다.

예를 들어 curiosity가 높다고 해서 항상 `explore`를 실행하도록 만들면 Personality가 사실상 imperative rule이 된다. 그러면 다른 욕구, 환경 조건, 최근 경험과 조합하기 어려워진다.

대신 Personality는 decision에 **기여하는 값**으로 다루는 편이 더 자연스럽다.

```text
curiosity
!= explore command

curiosity
→ exploration relevance에 기여

```

이 방식이면 같은 boredom 상태에서도 캐릭터마다 행동이 나타나는 시점이나 target preference가 달라질 수 있고, 그 차이를 실험으로 관찰할 수 있다.

## Memory도 단순 저장소가 아니다

Character Memory를 데이터베이스 기능으로 먼저 생각하지 않으려고 했다.

Character 관점에서 중요한 것은 “무엇을 저장했는가”보다 저장된 경험이 다음 선택에 어떤 의미를 갖는가다.

예를 들어:

- 이 target은 익숙한가
- 최근 같은 행동을 반복했는가
- 특정 대상에 affinity가 생겼는가
- 새로운 것을 선호해야 하는가
- 어떤 habit이 형성됐는가

이런 의미가 먼저 정의돼야 persistence 전략도 정할 수 있다.

그래서 초기 Character Core에서는 session-local memory만으로도 의미 모델을 검증할 수 있게 두고, reload를 넘는 영속성은 identity, migration, privacy, retention 같은 별도 계약이 준비된 뒤 붙이는 방향을 택했다.

## Rive를 채택해도 의미의 주인은 아니다

CharaWeave에서는 Rive를 advanced Presentation backend로 채택했다. 상태 머신과 인터랙션 표현 능력이 강하기 때문에 캐릭터 표현에는 잘 맞는다.

하지만 여기서도 경계는 명확해야 한다.

```text
Rive State Machine
!=
CharaWeave Character State Machine

```

Rive 안에 `happy`, `sleep`, `explore` 같은 상태 이름을 만들 수 있다고 해서 그 파일이 Character semantics의 source of truth가 되면 안 된다.

예를 들어 Character Core가 “에너지가 낮고 휴식 motivation이 선택됐다”는 semantic result를 만든 뒤 Presentation binding이 적절한 Rive state를 실행하는 구조는 괜찮다.

반대로 Rive animation이 끝났기 때문에 Character가 휴식을 필요로 했다고 역으로 정의하면 Presentation backend가 도메인 의미를 소유하게 된다.

이 차이는 Rive를 다른 backend로 교체할 때 크게 드러난다.

## AI 없이도 캐릭터가 의미 있어야 한다

또 하나의 중요한 원칙은 Character System이 LLM 호출 없이는 아무것도 못 하는 구조가 되지 않게 하는 것이다.

```text
NO AI
MUST STILL PRODUCE
A MEANINGFUL CHARACTER

```

Life State, Motivation, Personality, Memory와 행동 정책만으로도 기본적인 Character identity가 나타나야 한다. AI는 나중에 context 해석이나 advisory signal을 제공할 수 있지만, 이미 정의된 semantics를 보조하는 쪽이 안전하다.

이렇게 하면 비용이나 네트워크 상태 때문에 AI provider가 없어도 캐릭터는 작동하고, AI가 어떤 제안을 했는지와 실제 Character decision을 구분해서 관찰할 수도 있다.

## 이 설계가 아직 완성됐다는 뜻은 아니다

여기까지는 Character System의 방향과 책임 경계를 정리한 것이다. Runtime Foundation이 있다고 해서 Character Core가 자동으로 완성되는 것도 아니고, roadmap에 Life State와 Memory가 적혀 있다고 해서 모든 의미가 구현됐다는 뜻도 아니다.

오히려 지금 중요한 것은 각각의 vertical slice가 실제로 다음을 증명하는지 확인하는 일이다.

```text
state difference
→ explainable decision difference
→ observable runtime behavior
→ result feedback

```

그리고 같은 조건에서 Character configuration을 바꿨을 때 재현 가능한 행동 차이가 나오는지도 Lab에서 검증해야 한다.

## 결국 만들고 싶은 것은 애니메이션 묶음이 아니다

CharaWeave를 캐릭터 SDK라고 부르면 가장 먼저 보이는 것은 화면 위의 움직임이다. 하지만 장기적으로 더 중요한 자산은 그 움직임 아래의 의미 구조라고 생각한다.

Presentation은 계속 바뀔 수 있다. 이미지 기반 renderer에서 Rive로 갈 수도 있고, 다른 host에서는 전혀 다른 표현 방식을 쓸 수도 있다.

그때도 유지돼야 하는 것은 다음 질문에 대한 답이다.

> 이 캐릭터는 왜 지금 이 행동을 했는가?

그 답을 Runtime 좌표나 animation state가 아니라 Life, Motivation, Personality, Memory와 decision semantics로 설명할 수 있다면, 캐릭터는 단순히 움직이는 UI 요소에서 조금 더 멀리 갈 수 있다.