02 · 설치 및 링크 가이드
Story Engine SDK v1.2.0 아카이브(story-engine-v1.2.0-<platform>.zip)는 압축 해제 즉시 C11 이상 또는 C++11 이상의 호스트 프로젝트에서 링크할 수 있는 단일 헤더와 공유 라이브러리로 구성됩니다.
SDK 아카이브 디렉터리 레이아웃
story-engine-v1.2.0-<platform>/
├── bin/
│ ├── storyc # CLI 검증·컴파일·실행 도구 (macOS / Linux)
│ ├── storyc.exe # CLI 검증·컴파일·실행 도구 (Windows x64)
│ └── story_engine.dll # 런타임 공유 라이브러리 DLL (Windows x64)
├── include/
│ └── story_engine/
│ └── story_engine.h # 공개 C ABI 단일 헤더 (ABI v1, 27개 함수)
├── lib/
│ ├── libstory_engine.dylib # 공유 라이브러리 (macOS arm64 / macos-x64)
│ ├── libstory_engine.so # 공유 라이브러리 (Linux x64)
│ └── story_engine_import.lib # MSVC / clang-cl 임포트 라이브러리 (Windows x64)
├── docs/
│ └── examples/ # quickstart.c, run_quickstart.py, story.json, entry.json
├── licenses/
│ └── rapidjson.txt # 서드파티 오픈소스 라이선스 고지
└── manifest.json # 배포 파일별 SHA-256 체크섬 매니페스트
플랫폼별 직접 컴파일 명령 (C11)
압축 해제한 아카이브 루트 디렉터리에서 호스트 바이너리를 직접 빌드하는 명령입니다. Windows에서 story_engine.dll을 동적 링크할 때는 반드시 STORY_ENGINE_USE_SHARED 전처리기 매크로를 정의해 SE_API가 __declspec(dllimport)로 확장되도록 지정해야 합니다.
# macOS (macos-arm64 / macos-x64 — Apple Clang)
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-I./include docs/examples/quickstart.c \
-L./lib -lstory_engine -Wl,-rpath,"$(pwd)/lib" \
-o /tmp/quickstart
# Linux (linux-x64 — GCC / Clang)
cc -std=c11 -Wall -Wextra -Werror -pedantic \
-I./include docs/examples/quickstart.c \
-L./lib -lstory_engine -Wl,-rpath,"$(pwd)/lib" \
-o /tmp/quickstart
# Windows (windows-x64 — MSVC cl.exe, x64 Native Tools Prompt)
cl /std:c11 /W4 /DSTORY_ENGINE_USE_SHARED /I"include" ^
docs\examples\quickstart.c ^
/link /LIBPATH:"lib" story_engine_import.lib /OUT:quickstart.exe
# Windows (windows-x64 — LLVM clang-cl.exe)
clang-cl /std:c11 /W4 /DSTORY_ENGINE_USE_SHARED /I"include" ^
docs\examples\quickstart.c lib\story_engine_import.lib /Fe:quickstart.exe
CMake SHARED IMPORTED 타깃 연동
-DSTORY_ENGINE_PACKAGE=/abs/path/to/story-engine-v1.2.0-<platform>를 받아 플랫폼별 공유 라이브러리와 헤더, Windows STORY_ENGINE_USE_SHARED 매크로를 깔끔하게 캡슐화하는 CMakeLists.txt 구성입니다.
cmake_minimum_required(VERSION 3.18)
project(game_host LANGUAGES C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
if(NOT STORY_ENGINE_PACKAGE)
message(FATAL_ERROR "Pass -DSTORY_ENGINE_PACKAGE=/path/to/story-engine-v1.2.0-<platform>")
endif()
add_library(story_engine SHARED IMPORTED GLOBAL)
set_target_properties(story_engine PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${STORY_ENGINE_PACKAGE}/include"
)
if(WIN32)
set_target_properties(story_engine PROPERTIES
IMPORTED_LOCATION "${STORY_ENGINE_PACKAGE}/bin/story_engine.dll"
IMPORTED_IMPLIB "${STORY_ENGINE_PACKAGE}/lib/story_engine_import.lib"
INTERFACE_COMPILE_DEFINITIONS "STORY_ENGINE_USE_SHARED"
)
elseif(APPLE)
set_target_properties(story_engine PROPERTIES
IMPORTED_LOCATION "${STORY_ENGINE_PACKAGE}/lib/libstory_engine.dylib"
)
else()
set_target_properties(story_engine PROPERTIES
IMPORTED_LOCATION "${STORY_ENGINE_PACKAGE}/lib/libstory_engine.so"
)
endif()
add_executable(game_host src/main.c)
target_link_libraries(game_host PRIVATE story_engine)
미서명 바이너리 실행 유의사항: 현재 배포 바이너리에는 Apple 공증(notarization) 및 Windows Authenticode 상용 코드 서명이 적용되어 있지 않습니다. 반드시 공식 출처(choam.studio 또는 CHOAM-CO/602-releases) 및 SHA-256 해시를 먼저 확인한 뒤 아래 절차를 적용하십시오.
- macOS Gatekeeper 격리 해제: 압축 해제 폴더에서
xattr -dr com.apple.quarantine ./story-engine-v1.2.0-macos-arm64 (또는 story-editor-v0.1.2-macos-arm64) 실행
- Windows SmartScreen 및 DLL 경로: 출처 확인 후 추가 정보 → 실행 선택. 호스트 실행 시
bin\story_engine.dll을 실행 파일 옆에 복사하거나 PATH에 bin 폴더를 추가하십시오.
- Linux 실행 권한: 파일 권한이 유실된 경우
chmod +x ./bin/storyc (에디터는 chmod +x ./story-editor ./agent/story-agent) 실행
02 · Installation & Linking
Each Story Engine SDK v1.2.0 archive (story-engine-v1.2.0-<platform>.zip) extracts into a self-contained layout ready to link into any C11+ or C++11+ host project.
SDK Archive Directory Layout
story-engine-v1.2.0-<platform>/
├── bin/
│ ├── storyc # CLI validator, compiler & runner (macOS / Linux)
│ ├── storyc.exe # CLI validator, compiler & runner (Windows x64)
│ └── story_engine.dll # Runtime shared library DLL (Windows x64)
├── include/
│ └── story_engine/
│ └── story_engine.h # Single public C ABI header (ABI v1, 27 functions)
├── lib/
│ ├── libstory_engine.dylib # Shared library (macOS arm64 / macos-x64)
│ ├── libstory_engine.so # Shared library (Linux x64)
│ └── story_engine_import.lib # MSVC / clang-cl DLL import library (Windows x64)
├── docs/
│ └── examples/ # quickstart.c, run_quickstart.py, story.json, entry.json
├── licenses/
│ └── rapidjson.txt # Third-party open-source license notice
└── manifest.json # Per-file SHA-256 checksum manifest
Direct Compiler Commands (C11)
Run these commands from the extracted archive root. When linking dynamically against story_engine.dll on Windows, define STORY_ENGINE_USE_SHARED so SE_API expands to __declspec(dllimport).
# macOS (macos-arm64 / macos-x64 — Apple Clang)
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-I./include docs/examples/quickstart.c \
-L./lib -lstory_engine -Wl,-rpath,"$(pwd)/lib" \
-o /tmp/quickstart
# Linux (linux-x64 — GCC / Clang)
cc -std=c11 -Wall -Wextra -Werror -pedantic \
-I./include docs/examples/quickstart.c \
-L./lib -lstory_engine -Wl,-rpath,"$(pwd)/lib" \
-o /tmp/quickstart
# Windows (windows-x64 — MSVC cl.exe, x64 Native Tools Prompt)
cl /std:c11 /W4 /DSTORY_ENGINE_USE_SHARED /I"include" ^
docs\examples\quickstart.c ^
/link /LIBPATH:"lib" story_engine_import.lib /OUT:quickstart.exe
# Windows (windows-x64 — LLVM clang-cl.exe)
clang-cl /std:c11 /W4 /DSTORY_ENGINE_USE_SHARED /I"include" ^
docs\examples\quickstart.c lib\story_engine_import.lib /Fe:quickstart.exe
CMake SHARED IMPORTED Target
Pass -DSTORY_ENGINE_PACKAGE=/abs/path/to/story-engine-v1.2.0-<platform> to encapsulate include paths, shared library locations, and Windows import definitions cleanly:
cmake_minimum_required(VERSION 3.18)
project(game_host LANGUAGES C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
if(NOT STORY_ENGINE_PACKAGE)
message(FATAL_ERROR "Pass -DSTORY_ENGINE_PACKAGE=/path/to/story-engine-v1.2.0-<platform>")
endif()
add_library(story_engine SHARED IMPORTED GLOBAL)
set_target_properties(story_engine PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${STORY_ENGINE_PACKAGE}/include"
)
if(WIN32)
set_target_properties(story_engine PROPERTIES
IMPORTED_LOCATION "${STORY_ENGINE_PACKAGE}/bin/story_engine.dll"
IMPORTED_IMPLIB "${STORY_ENGINE_PACKAGE}/lib/story_engine_import.lib"
INTERFACE_COMPILE_DEFINITIONS "STORY_ENGINE_USE_SHARED"
)
elseif(APPLE)
set_target_properties(story_engine PROPERTIES
IMPORTED_LOCATION "${STORY_ENGINE_PACKAGE}/lib/libstory_engine.dylib"
)
else()
set_target_properties(story_engine PROPERTIES
IMPORTED_LOCATION "${STORY_ENGINE_PACKAGE}/lib/libstory_engine.so"
)
endif()
add_executable(game_host src/main.c)
target_link_libraries(game_host PRIVATE story_engine)
Unsigned Binary Handling: Current release archives are not Apple-notarized (ad-hoc signed on macOS) and do not carry Windows Authenticode certificates. Always verify the archive origin (choam.studio or CHOAM-CO/602-releases) and SHA-256 checksum first:
- macOS Quarantine Removal: Run
xattr -dr com.apple.quarantine ./story-engine-v1.2.0-macos-arm64 (or ./story-editor-v0.1.2-macos-arm64) on the extracted directory.
- Windows SmartScreen & DLL Search Path: Confirm the file origin if SmartScreen prompts (More info → Run anyway), and copy
bin\story_engine.dll next to your executable or add bin to PATH.
- Linux Executable Permissions: Ensure execute bits are set via
chmod +x ./bin/storyc (or chmod +x ./story-editor ./agent/story-agent for the Editor archive).
02 · インストールとリンク
Story Engine SDK v1.2.0 の配布アーカイブ(story-engine-v1.2.0-<platform>.zip)を展開すると、C11 以上または C++11 以上のホストプロジェクトからそのままリンクできる単一ヘッダと共有ライブラリが揃っています。
SDK アーカイブのディレクトリ構成
story-engine-v1.2.0-<platform>/
├── bin/
│ ├── storyc # CLI 検証・コンパイル・実行ツール (macOS / Linux)
│ ├── storyc.exe # CLI 検証・コンパイル・実行ツール (Windows x64)
│ └── story_engine.dll # ランタイム共有ライブラリ DLL (Windows x64)
├── include/
│ └── story_engine/
│ └── story_engine.h # 公開 C ABI 単一ヘッダ (ABI v1, 全27関数)
├── lib/
│ ├── libstory_engine.dylib # 共有ライブラリ (macOS arm64 / macos-x64)
│ ├── libstory_engine.so # 共有ライブラリ (Linux x64)
│ └── story_engine_import.lib # MSVC / clang-cl 用インポートライブラリ (Windows x64)
├── docs/
│ └── examples/ # quickstart.c, run_quickstart.py, story.json, entry.json
├── licenses/
│ └── rapidjson.txt # サードパーティ OSS ライセンス表記
└── manifest.json # ファイル別 SHA-256 チェックサムマニフェスト
コンパイラ直接呼び出しによるビルド(C11)
展開したアーカイブのルートディレクトリからビルドする例です。Windows で story_engine.dll を動的リンクする際は、SE_API を __declspec(dllimport) に展開するため STORY_ENGINE_USE_SHARED マクロを定義してください。
# macOS (macos-arm64 / macos-x64 — Apple Clang)
clang -std=c11 -Wall -Wextra -Werror -pedantic \
-I./include docs/examples/quickstart.c \
-L./lib -lstory_engine -Wl,-rpath,"$(pwd)/lib" \
-o /tmp/quickstart
# Linux (linux-x64 — GCC / Clang)
cc -std=c11 -Wall -Wextra -Werror -pedantic \
-I./include docs/examples/quickstart.c \
-L./lib -lstory_engine -Wl,-rpath,"$(pwd)/lib" \
-o /tmp/quickstart
# Windows (windows-x64 — MSVC cl.exe, x64 Native Tools Prompt)
cl /std:c11 /W4 /DSTORY_ENGINE_USE_SHARED /I"include" ^
docs\examples\quickstart.c ^
/link /LIBPATH:"lib" story_engine_import.lib /OUT:quickstart.exe
# Windows (windows-x64 — LLVM clang-cl.exe)
clang-cl /std:c11 /W4 /DSTORY_ENGINE_USE_SHARED /I"include" ^
docs\examples\quickstart.c lib\story_engine_import.lib /Fe:quickstart.exe
CMake SHARED IMPORTED ターゲット設定
-DSTORY_ENGINE_PACKAGE=/abs/path/to/story-engine-v1.2.0-<platform> を渡すことで、各 OS の共有ライブラリと STORY_ENGINE_USE_SHARED 定義を一元管理できます。
cmake_minimum_required(VERSION 3.18)
project(game_host LANGUAGES C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
if(NOT STORY_ENGINE_PACKAGE)
message(FATAL_ERROR "Pass -DSTORY_ENGINE_PACKAGE=/path/to/story-engine-v1.2.0-<platform>")
endif()
add_library(story_engine SHARED IMPORTED GLOBAL)
set_target_properties(story_engine PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${STORY_ENGINE_PACKAGE}/include"
)
if(WIN32)
set_target_properties(story_engine PROPERTIES
IMPORTED_LOCATION "${STORY_ENGINE_PACKAGE}/bin/story_engine.dll"
IMPORTED_IMPLIB "${STORY_ENGINE_PACKAGE}/lib/story_engine_import.lib"
INTERFACE_COMPILE_DEFINITIONS "STORY_ENGINE_USE_SHARED"
)
elseif(APPLE)
set_target_properties(story_engine PROPERTIES
IMPORTED_LOCATION "${STORY_ENGINE_PACKAGE}/lib/libstory_engine.dylib"
)
else()
set_target_properties(story_engine PROPERTIES
IMPORTED_LOCATION "${STORY_ENGINE_PACKAGE}/lib/libstory_engine.so"
)
endif()
add_executable(game_host src/main.c)
target_link_libraries(game_host PRIVATE story_engine)
未署名バイナリの取り扱いについて: 現在の配布バイナリには Apple 公証(notarization)および Windows Authenticode 商用署名が適用されていません(macOS は ad-hoc 署名)。必ず公式配布元(choam.studio または CHOAM-CO/602-releases)および SHA-256 ハッシュを確認した上で以下の手順を行ってください。
- macOS 隔離属性の解除: 展開先に対して
xattr -dr com.apple.quarantine ./story-engine-v1.2.0-macos-arm64(Editor は ./story-editor-v0.1.2-macos-arm64)を実行します。
- Windows SmartScreen と DLL 配置: 配布元を確認後「詳細情報 → 実行」を選択し、実行時は
bin\story_engine.dll を実行ファイルと同じフォルダに配置するか bin を PATH に追加してください。
- Linux 実行権限: 必要に応じて
chmod +x ./bin/storyc(Editor は chmod +x ./story-editor ./agent/story-agent)を付与します。
03 · 빠른 시작 (Quickstart)
아래 예제는 story-engine/turn-source/1 포맷으로 상점 통행증 등록 시나리오를 정의하고, storyc CLI로 검증·컴파일한 뒤 C11 호스트 프로그램에서 실행하는 전체 과정을 보여줍니다.
1. 스토리 정의 (story.json) 및 진입점 (entry.json)
{
"format": "story-engine/turn-source/1",
"name": "Guild Pass Quickstart",
"types": [
{ "id": "Coins", "value_type": { "kind": "int" } }
],
"entities": [
{ "id": "traveler", "name": "Traveler" },
{ "id": "clerk", "name": "Guild Clerk" }
],
"states": [
{
"id": "coins",
"owner": "traveler",
"declared_type": "Coins",
"initial": { "kind": "int", "value": "50" }
}
],
"expressions": [
{ "id": "coins_val", "op": "state", "state": "coins" },
{ "id": "fee", "op": "literal", "literal": { "kind": "int", "value": "20" } },
{ "id": "can_afford", "op": "greater_equal", "arguments": ["coins_val", "fee"] }
],
"snapshots": [
{ "id": "opening_ledger", "states": ["coins"] }
],
"turns": [
{
"id": "record_opening",
"kind": "capture",
"snapshot": "opening_ledger",
"next": "greet"
},
{
"id": "greet",
"kind": "dialogue",
"actor": "clerk",
"text": "Register a guild pass for 20 coins?",
"next": "decide"
},
{
"id": "decide",
"kind": "choice",
"text": "Select your response:",
"choices": [
{ "id": "pay", "text": "Pay 20 coins", "when": "can_afford", "next": "charge" },
{ "id": "leave", "text": "Decline", "next": "done" }
]
},
{
"id": "charge",
"kind": "update",
"updates": [
{ "target": "coins", "op": "subtract", "expr": "fee" }
],
"next": "done"
},
{ "id": "done", "kind": "end" }
]
}
시작 턴을 지정하는 entry.json 파일입니다:
{ "turn": "record_opening" }
2. storyc CLI로 검증, 컴파일 및 대화형 실행
# 1) 정적 검증 — 오류·경고가 없으면 stdout에 [] 출력 후 종료 코드 0
./bin/storyc check story.json
# 2) 불투명(opaque) 바이너리 아티팩트로 컴파일
./bin/storyc compile story.json /tmp/guild.story
# 3) 시드 42로 터미널에서 대화형 시뮬레이션 실행
./bin/storyc run /tmp/guild.story entry.json 42
3. 최소 C11 호스트 통합 코드 (host_quickstart.c)
se_session_advance_to_yield를 사용하면 내부 비가시 턴(capture, update)을 예산(max_steps) 범위 안에서 원자적으로 통과하고 첫 외부 노출 턴(dialogue, action, choice, end)에서 제어를 돌려받습니다.
#include "story_engine/story_engine.h"
#include <stdio.h>
static void print_and_release(se_buffer *buf) {
if (buf->data) {
fwrite(buf->data, 1, buf->size, stdout);
fputc('\n', stdout);
}
se_buffer_release(buf);
}
int main(void) {
if (se_abi_version() != 1u) return 1;
const char *source_json =
"{\"format\":\"story-engine/turn-source/1\","
"\"types\":[{\"id\":\"Coins\",\"value_type\":\"int\"}],"
"\"entities\":[{\"id\":\"clerk\"}],"
"\"states\":[{\"id\":\"coins\",\"owner\":\"clerk\",\"declared_type\":\"Coins\",\"initial\":50}],"
"\"turns\":["
"{\"id\":\"open\",\"kind\":\"update\",\"updates\":[{\"target\":\"coins\",\"op\":\"subtract\",\"value\":10}],\"next\":\"greet\"},"
"{\"id\":\"greet\",\"kind\":\"dialogue\",\"actor\":\"clerk\",\"text\":\"Pass issued.\",\"next\":\"done\"},"
"{\"id\":\"done\",\"kind\":\"end\"}"
"]}";
se_program program = 0;
se_session session = 0;
se_buffer out = {0};
int rc = 1;
if (se_compile(source_json, &program, &out) != SE_OK) {
print_and_release(&out);
goto cleanup;
}
se_buffer_release(&out);
if (se_session_create(program, 42ULL, &session, &out) != SE_OK) goto cleanup;
se_buffer_release(&out);
if (se_session_start(session, "{\"turn\":\"open\"}", &out) != SE_OK) goto cleanup;
se_buffer_release(&out);
/* 내부 update("open")를 거친 뒤 첫 가시 턴("greet")과 누적 changes를 반환 */
if (se_session_advance_to_yield(session, NULL, 16, &out) != SE_OK) goto cleanup;
print_and_release(&out);
/* 종료 턴("done") 실행 -> phase == "ended" */
if (se_session_advance_to_yield(session, NULL, 16, &out) != SE_OK) goto cleanup;
print_and_release(&out);
rc = 0;
cleanup:
se_buffer_release(&out);
if (session != 0) se_session_release(session);
if (program != 0) se_program_release(program);
return rc;
}
03 · Quickstart
This walkthrough defines a compact guild-pass scenario in story-engine/turn-source/1 JSON, validates and compiles it with storyc, and drives it from a minimal C11 host program.
1. Author story.json & entry.json
{
"format": "story-engine/turn-source/1",
"name": "Guild Pass Quickstart",
"types": [
{ "id": "Coins", "value_type": { "kind": "int" } }
],
"entities": [
{ "id": "traveler", "name": "Traveler" },
{ "id": "clerk", "name": "Guild Clerk" }
],
"states": [
{
"id": "coins",
"owner": "traveler",
"declared_type": "Coins",
"initial": { "kind": "int", "value": "50" }
}
],
"expressions": [
{ "id": "coins_val", "op": "state", "state": "coins" },
{ "id": "fee", "op": "literal", "literal": { "kind": "int", "value": "20" } },
{ "id": "can_afford", "op": "greater_equal", "arguments": ["coins_val", "fee"] }
],
"snapshots": [
{ "id": "opening_ledger", "states": ["coins"] }
],
"turns": [
{
"id": "record_opening",
"kind": "capture",
"snapshot": "opening_ledger",
"next": "greet"
},
{
"id": "greet",
"kind": "dialogue",
"actor": "clerk",
"text": "Register a guild pass for 20 coins?",
"next": "decide"
},
{
"id": "decide",
"kind": "choice",
"text": "Select your response:",
"choices": [
{ "id": "pay", "text": "Pay 20 coins", "when": "can_afford", "next": "charge" },
{ "id": "leave", "text": "Decline", "next": "done" }
]
},
{
"id": "charge",
"kind": "update",
"updates": [
{ "target": "coins", "op": "subtract", "expr": "fee" }
],
"next": "done"
},
{ "id": "done", "kind": "end" }
]
}
Create entry.json to select the entry turn:
{ "turn": "record_opening" }
2. Check, Compile & Run via storyc
# 1) Validate source JSON — prints [] to stdout and exits 0 when clean
./bin/storyc check story.json
# 2) Compile into an opaque binary story artifact
./bin/storyc compile story.json /tmp/guild.story
# 3) Run interactively from stdin/stdout with deterministic RNG seed 42
./bin/storyc run /tmp/guild.story entry.json 42
3. Minimal C11 Host Program (host_quickstart.c)
Calling se_session_advance_to_yield executes internal non-yielding turns (update, capture) atomically within max_steps and stops at the first externally observable turn (dialogue, action, choice, end) or open group exit.
#include "story_engine/story_engine.h"
#include <stdio.h>
static void print_and_release(se_buffer *buf) {
if (buf->data) {
fwrite(buf->data, 1, buf->size, stdout);
fputc('\n', stdout);
}
se_buffer_release(buf);
}
int main(void) {
if (se_abi_version() != 1u) return 1;
const char *source_json =
"{\"format\":\"story-engine/turn-source/1\","
"\"types\":[{\"id\":\"Coins\",\"value_type\":\"int\"}],"
"\"entities\":[{\"id\":\"clerk\"}],"
"\"states\":[{\"id\":\"coins\",\"owner\":\"clerk\",\"declared_type\":\"Coins\",\"initial\":50}],"
"\"turns\":["
"{\"id\":\"open\",\"kind\":\"update\",\"updates\":[{\"target\":\"coins\",\"op\":\"subtract\",\"value\":10}],\"next\":\"greet\"},"
"{\"id\":\"greet\",\"kind\":\"dialogue\",\"actor\":\"clerk\",\"text\":\"Pass issued.\",\"next\":\"done\"},"
"{\"id\":\"done\",\"kind\":\"end\"}"
"]}";
se_program program = 0;
se_session session = 0;
se_buffer out = {0};
int rc = 1;
if (se_compile(source_json, &program, &out) != SE_OK) {
print_and_release(&out);
goto cleanup;
}
se_buffer_release(&out);
if (se_session_create(program, 42ULL, &session, &out) != SE_OK) goto cleanup;
se_buffer_release(&out);
if (se_session_start(session, "{\"turn\":\"open\"}", &out) != SE_OK) goto cleanup;
se_buffer_release(&out);
/* Executes internal update ("open") and yields at observable dialogue ("greet") */
if (se_session_advance_to_yield(session, NULL, 16, &out) != SE_OK) goto cleanup;
print_and_release(&out);
/* Advances into "done" -> phase becomes "ended" */
if (se_session_advance_to_yield(session, NULL, 16, &out) != SE_OK) goto cleanup;
print_and_release(&out);
rc = 0;
cleanup:
se_buffer_release(&out);
if (session != 0) se_session_release(session);
if (program != 0) se_program_release(program);
return rc;
}
03 · クイックスタート
story-engine/turn-source/1 形式でギルド通行証シナリオを記述し、storyc CLI で検証・コンパイルした上で、最小構成の C11 ホストプログラムから駆動する手順を示します。
1. ストーリー定義(story.json)と開始設定(entry.json)
{
"format": "story-engine/turn-source/1",
"name": "Guild Pass Quickstart",
"types": [
{ "id": "Coins", "value_type": { "kind": "int" } }
],
"entities": [
{ "id": "traveler", "name": "Traveler" },
{ "id": "clerk", "name": "Guild Clerk" }
],
"states": [
{
"id": "coins",
"owner": "traveler",
"declared_type": "Coins",
"initial": { "kind": "int", "value": "50" }
}
],
"expressions": [
{ "id": "coins_val", "op": "state", "state": "coins" },
{ "id": "fee", "op": "literal", "literal": { "kind": "int", "value": "20" } },
{ "id": "can_afford", "op": "greater_equal", "arguments": ["coins_val", "fee"] }
],
"snapshots": [
{ "id": "opening_ledger", "states": ["coins"] }
],
"turns": [
{
"id": "record_opening",
"kind": "capture",
"snapshot": "opening_ledger",
"next": "greet"
},
{
"id": "greet",
"kind": "dialogue",
"actor": "clerk",
"text": "Register a guild pass for 20 coins?",
"next": "decide"
},
{
"id": "decide",
"kind": "choice",
"text": "Select your response:",
"choices": [
{ "id": "pay", "text": "Pay 20 coins", "when": "can_afford", "next": "charge" },
{ "id": "leave", "text": "Decline", "next": "done" }
]
},
{
"id": "charge",
"kind": "update",
"updates": [
{ "target": "coins", "op": "subtract", "expr": "fee" }
],
"next": "done"
},
{ "id": "done", "kind": "end" }
]
}
開始ターンを指定する entry.json を作成します:
{ "turn": "record_opening" }
2. storyc CLI による検証・コンパイル・対話実行
# 1) 静的検証 — エラー・警告がなければ stdout に [] を出力して終了コード 0
./bin/storyc check story.json
# 2) 不透明(opaque)バイナリアーティファクトへコンパイル
./bin/storyc compile story.json /tmp/guild.story
# 3) 乱数シード 42 で標準入出力から対話実行
./bin/storyc run /tmp/guild.story entry.json 42
3. 最小構成の C11 ホストプログラム(host_quickstart.c)
se_session_advance_to_yield を呼び出すと、内部ターン(update・capture)をステップ予算(max_steps)内でアトミックに連続実行し、最初の外部可視ターン(dialogue・action・choice・end)またはグループ出口で制御を返します。
#include "story_engine/story_engine.h"
#include <stdio.h>
static void print_and_release(se_buffer *buf) {
if (buf->data) {
fwrite(buf->data, 1, buf->size, stdout);
fputc('\n', stdout);
}
se_buffer_release(buf);
}
int main(void) {
if (se_abi_version() != 1u) return 1;
const char *source_json =
"{\"format\":\"story-engine/turn-source/1\","
"\"types\":[{\"id\":\"Coins\",\"value_type\":\"int\"}],"
"\"entities\":[{\"id\":\"clerk\"}],"
"\"states\":[{\"id\":\"coins\",\"owner\":\"clerk\",\"declared_type\":\"Coins\",\"initial\":50}],"
"\"turns\":["
"{\"id\":\"open\",\"kind\":\"update\",\"updates\":[{\"target\":\"coins\",\"op\":\"subtract\",\"value\":10}],\"next\":\"greet\"},"
"{\"id\":\"greet\",\"kind\":\"dialogue\",\"actor\":\"clerk\",\"text\":\"Pass issued.\",\"next\":\"done\"},"
"{\"id\":\"done\",\"kind\":\"end\"}"
"]}";
se_program program = 0;
se_session session = 0;
se_buffer out = {0};
int rc = 1;
if (se_compile(source_json, &program, &out) != SE_OK) {
print_and_release(&out);
goto cleanup;
}
se_buffer_release(&out);
if (se_session_create(program, 42ULL, &session, &out) != SE_OK) goto cleanup;
se_buffer_release(&out);
if (se_session_start(session, "{\"turn\":\"open\"}", &out) != SE_OK) goto cleanup;
se_buffer_release(&out);
/* 内部 update ("open") を通過し、可視ターン ("greet") と累積 changes を取得 */
if (se_session_advance_to_yield(session, NULL, 16, &out) != SE_OK) goto cleanup;
print_and_release(&out);
/* 終了ターン ("done") を実行 -> phase == "ended" */
if (se_session_advance_to_yield(session, NULL, 16, &out) != SE_OK) goto cleanup;
print_and_release(&out);
rc = 0;
cleanup:
se_buffer_release(&out);
if (session != 0) se_session_release(session);
if (program != 0) se_program_release(program);
return rc;
}
05 · C ABI 레퍼런스 (story_engine/story_engine.h)
Story Engine 공유 라이브러리는 C++ 예외가 경계를 넘지 않도록 완벽히 격리된 27개의 C11 ABI 함수만 외부 심볼로 노출합니다. 런타임 초기화 시 se_abi_version() == 1u를 확인하여 로드된 바이너리의 ABI 호환성을 검증하십시오.
상수 매크로 및 상태 코드 (se_status)
| 심볼 / 상수 |
정의 / 값 |
설명 |
se_abi_version() | 1u (ABI Version 1) | 런타임에 로드된 공유 라이브러리의 공개 C ABI 버전 번호. |
SE_MAX_JSON_BYTES | 8,388,608 (8 MiB) | 모든 UTF-8 JSON 및 텍스트 입출력의 최대 바이트 길이 (종단 NUL 제외). |
SE_MAX_ARTIFACT_BYTES | 8,392,704 (8 MiB + 4 KiB) | se_program_load가 허용하는 불투명 바이너리 아티팩트 최대 바이트 크기. |
SE_MAX_CHECKPOINT_BYTES | 16,777,216 (16 MiB) | se_session_load가 허용하는 불투명 바이너리 체크포인트 최대 바이트 크기. |
SE_OK | 0 | 호출 성공. 출력 se_buffer에 결과 JSON 또는 바이너리 바이트열 기록. |
SE_ERROR | 1 | JSON 문법·스키마 오류, invalid_phase, pending_choice, readonly_state, 런타임 산술·범위 오류(invalid_range 등). |
SE_INVALID_ARGUMENT | 2 | 필수 포인터 NULL, 크기 한도 초과, 비어 있지 않은 출력 se_buffer 전달, max_steps == 0 등. |
SE_INVALID_HANDLE | 3 | 0, 이미 해제된 핸들, 존재하지 않거나 종류가 다른 핸들(se_program/se_session/se_object) 전달. |
SE_COMPILE_ERROR | 4 | 정적 의미 검증 실패 또는 손상된 아티팩트(error.code == "compile_error", diagnostics 배열 동반). |
SE_OUT_OF_MEMORY | 5 | 메모리 할당 실패. 오류 JSON 버퍼조차 할당되지 못해 data == NULL일 수 있으므로 상태 코드를 먼저 확인하십시오. |
se_buffer 및 핸들 소유권 규칙
- 출력 버퍼
{0} 초기화 필수: 모든 출력 se_buffer는 호출 전 반드시 se_buffer out = {0}; 상태여야 합니다. data != NULL이거나 size != 0인 버퍼를 전달하면 기존 데이터를 덮어쓰지 않고 즉시 SE_INVALID_ARGUMENT를 반환합니다.
- 해제 의무 및 멱등성: 성공 여부와 무관하게
out.data != NULL이면 사용 후 반드시 se_buffer_release(&out)로 해제하십시오(호출자의 free() 사용 금지, 구조체 값 복사로 중복 해제 금지). 해제된 구조체는 자동으로 {0}으로 리셋됩니다.
- 텍스트 JSON vs 불투명 바이너리 출력: JSON 출력 함수는 편의를 위해
data[size]에 여분의 '\0'을 덧붙이지만(size는 종단 NUL 제외), se_program_artifact와 se_session_save가 반환하는 불투명 바이너리 버퍼는 내부에 0x00 바이트를 포함할 수 있으므로 strlen을 쓰지 말고 정확히 size 바이트만 처리하십시오.
- 핸들 수명 및 스레드 안전성:
se_program, se_session, se_object는 모두 0이 아닌 uint64_t ID이며 한 라이브러리 인스턴스 내에서 재사용되지 않습니다. 엔진 내부 레지스트리가 동시 C ABI 호출을 직렬화하며, 진행 중인 호출이 참조하는 입력 메모리와 출력 se_buffer 구조체는 호출 완료 전까지 변경·해제해서는 안 됩니다.
전체 27개 공개 함수 요약 표
| 분류 |
함수 이름 |
핵심 동작 및 출력 계약 |
| 버전·버퍼 (2) | se_abi_version | 로드된 라이브러리의 ABI 버전(1u) 반환. |
| se_buffer_release | 엔진 소유 se_buffer 메모리를 해제하고 {0}으로 초기화 (NULL/빈 버퍼에 멱등). |
| 프로그램 (6) | se_compile | story-engine/turn-source/1 UTF-8 JSON을 검증·컴파일해 se_program 핸들과 진단 배열 반환. |
| se_program_load | 불투명 바이너리 아티팩트(artifact, size)를 검증·로드하여 se_program 핸들 발행. |
| se_program_artifact | 컴파일된 프로그램을 불투명 바이너리 바이트 버퍼(data + size)로 직렬화. |
| se_program_inspect | 프로그램 지문, 타입, 엔티티 상속 계층, 상태, 스냅샷, 그룹, 턴 카탈로그 메타데이터 JSON 반환. |
| se_program_retain | 독립적으로 해제 가능한 새로운 se_program 핸들 발행. |
| se_program_release | se_program 핸들 해제 (활성 세션은 내부적으로 프로그램을 유지). |
| 세션 수명·저장 (6) | se_session_create | "idle" 단계의 새 세션 생성 (seed로 가중 무작위 경로 난수 제어). |
| se_session_clone | 추론 실행·즉시 롤백을 위한 독립 런타임 세션 복제본 생성(외부 객체 임대는 제외). |
| se_session_save | 현재 세션을 불투명 바이너리 체크포인트 바이트 버퍼(data + size)로 직렬화. |
| se_session_load | 원본 program과 대조 검증하여 불투명 바이너리 체크포인트로부터 새 세션 복원. |
| se_session_retain | 동일 세션을 가리키는 독립 se_session 핸들 추가 발행. |
| se_session_release | se_session 핸들 해제 (진행 중인 호출이 있으면 완료 후 종료 대기열로 이동). |
| 실행·상태·평가 (6) | se_session_start | "idle" 세션에 진입 턴/그룹 결속(entry_json)을 설정해 "ready"로 전환. |
| se_session_advance | 주 작업 턴 1개를 실행하거나 answer_json으로 대기 중인 선택을 완료 후 후속 턴 1개 실행. 선택 응답에 선택적 updates(외부 상태 일괄 갱신; internal 상태 쓰기는 readonly_state로 거부)와 bindings를 포함하면 option.next 경로 해석 전에 원자적으로 먼저 적용되어 결과 changes 맨 앞에 기록되며, 어떤 오류라도 발생하면 호출 전체가 awaiting_choice 상태로 완전 롤백됨. |
| se_session_advance_to_yield | 내부 update/capture 턴을 max_steps 예산 내에서 연속 실행하여 첫 가시 턴 또는 출구에서 반환. 선택 응답의 선택적 updates/bindings 선행 원자적 적용·changes 선두 기록·실패 시 awaiting_choice 전체 롤백 계약을 동일하게 지원. |
| se_session_read | 세션 진행 없이 지정한 StateRef 배열의 현재 정규화 값 맵을 원자적으로 조회. |
| se_session_update | 호스트에서 외부 변경 가능(external) 상태들을 원자적 배치로 일괄 갱신. |
| se_session_evaluate | 부작용 없이 현재 세션 상태 기준으로 expression_id를 순수 평가. |
| 스냅샷·뷰·정리 (3) | se_session_read_snapshot | capture 턴이 생성한 불변 스냅샷 레코드("<snapshot>#N") 값 조회. |
| se_session_view | 현재 phase, next, pending_turn, request, version 요약 조회. |
| se_session_collect | 활성 세션 내부의 불필요해진 객체를 budget 한도 내에서 정리. |
| 외부 객체·전역 정리 (4) | se_object_retain | 상태·엔티티·스냅샷 레코드 ID에 대한 외부 보유 핸들(se_object) 획득. |
| se_object_read | 보유한 상태 se_object 값을 JSON으로 조회 (세션 핸들 해제 후에도 읽기 가능). |
| se_object_release | 외부 se_object 보유 핸들 해제. |
| se_collect_retired | 해제된 은퇴 세션들의 물리 자원을 budget 한도 내에서 회수. |
전체 C 헤더 프로토타입 (27개 심볼)
typedef uint64_t se_program;
typedef uint64_t se_session;
typedef uint64_t se_object;
typedef enum se_status {
SE_OK = 0,
SE_ERROR = 1,
SE_INVALID_ARGUMENT = 2,
SE_INVALID_HANDLE = 3,
SE_COMPILE_ERROR = 4,
SE_OUT_OF_MEMORY = 5
} se_status;
typedef struct se_buffer {
char *data;
size_t size;
} se_buffer;
#define SE_MAX_JSON_BYTES ((size_t)8 * 1024 * 1024)
#define SE_MAX_ARTIFACT_BYTES (SE_MAX_JSON_BYTES + (size_t)4096)
#define SE_MAX_CHECKPOINT_BYTES ((size_t)16 * 1024 * 1024)
/* 1–2: ABI Version & Buffer Ownership */
SE_API uint32_t se_abi_version(void);
SE_API void se_buffer_release(se_buffer *buffer);
/* 3–8: Program Compilation, Artifact I/O, Inspection & Lifetime */
SE_API se_status se_compile(const char *source_json, se_program *program, se_buffer *diagnostics);
SE_API se_status se_program_load(const void *artifact, size_t size, se_program *program, se_buffer *diagnostics);
SE_API se_status se_program_artifact(se_program program, se_buffer *artifact);
SE_API se_status se_program_inspect(se_program program, se_buffer *result);
SE_API se_status se_program_retain(se_program program, se_program *retained);
SE_API se_status se_program_release(se_program program);
/* 9–14: Session Creation, Cloning, Opaque Checkpoints & Lifetime */
SE_API se_status se_session_create(se_program program, uint64_t seed, se_session *session, se_buffer *result);
SE_API se_status se_session_clone(se_session source, se_session *cloned, se_buffer *result);
SE_API se_status se_session_save(se_session session, se_buffer *checkpoint);
SE_API se_status se_session_load(se_program program, const void *checkpoint, size_t size, se_session *session, se_buffer *result);
SE_API se_status se_session_retain(se_session session, se_session *retained);
SE_API se_status se_session_release(se_session session);
/* 15–23: Turn Stepping, State Access, Pure Evaluation, Snapshots & Maintenance */
SE_API se_status se_session_start(se_session session, const char *entry_json, se_buffer *result);
SE_API se_status se_session_advance(se_session session, const char *answer_json, se_buffer *result);
SE_API se_status se_session_advance_to_yield(se_session session, const char *answer_json, size_t max_steps, se_buffer *result);
SE_API se_status se_session_read(se_session session, const char *refs_json, se_buffer *result);
SE_API se_status se_session_update(se_session session, const char *assignments_json, const char *bindings_json, se_buffer *result);
SE_API se_status se_session_evaluate(se_session session, const char *expression_id, const char *bindings_json, se_buffer *result);
SE_API se_status se_session_read_snapshot(se_session session, const char *record_id, se_buffer *result);
SE_API se_status se_session_view(se_session session, se_buffer *result);
SE_API se_status se_session_collect(se_session session, size_t budget, se_buffer *result);
/* 24–27: External Object Leases & Retired Session Reclamation */
SE_API se_status se_object_retain(se_session session, const char *object_id, se_object *object, se_buffer *result);
SE_API se_status se_object_read(se_object object, se_buffer *result);
SE_API se_status se_object_release(se_object object);
SE_API se_status se_collect_retired(size_t budget, se_buffer *result);
05 · C ABI Reference (story_engine/story_engine.h)
The Story Engine shared library exports exactly 27 C11 functions across an exception-safe C boundary. Verify se_abi_version() == 1u during host startup before calling other entry points.
Limit Constants & Status Codes (se_status)
| Symbol / Constant |
Value |
Contract |
se_abi_version() | 1u (ABI Version 1) | Runtime C ABI version number exposed by the loaded shared library. |
SE_MAX_JSON_BYTES | 8,388,608 (8 MiB) | Maximum byte length for UTF-8 JSON and text inputs/outputs (excluding trailing NUL). |
SE_MAX_ARTIFACT_BYTES | 8,392,704 (8 MiB + 4 KiB) | Maximum input byte size accepted by se_program_load. |
SE_MAX_CHECKPOINT_BYTES | 16,777,216 (16 MiB) | Maximum opaque binary checkpoint byte size accepted by se_session_load. |
SE_OK | 0 | Operation succeeded; output se_buffer holds the result payload. |
SE_ERROR | 1 | JSON syntax/schema error, invalid_phase, pending_choice, readonly_state, or runtime math/range fault (e.g., invalid_range). |
SE_INVALID_ARGUMENT | 2 | Required pointer is NULL, size cap exceeded, non-empty output se_buffer, or max_steps == 0. |
SE_INVALID_HANDLE | 3 | Handle is 0, already released, unknown, or of the wrong handle kind. |
SE_COMPILE_ERROR | 4 | Static semantic validation or artifact check failed (error.code == "compile_error" with diagnostics[]). |
SE_OUT_OF_MEMORY | 5 | Allocation failed; output se_buffer may remain {0}—always inspect se_status and buf.data != NULL first. |
se_buffer Ownership & Handle Concurrency Rules
- Zero-Initialize Output Buffers: Every output
se_buffer must be initialized to {0} before a call. Passing a non-empty buffer (data != NULL or size != 0) returns SE_INVALID_ARGUMENT without overwriting existing data.
- Release via
se_buffer_release: Release every populated buffer (on both SE_OK and error envelopes) with se_buffer_release(&buf) before reuse. It is idempotent on NULL or {0} and resets the struct to {0}. Never pass buf.data to free() or shallow-copy the struct for multiple releases.
- Text JSON vs. Opaque Binary Outputs: JSON outputs append a convenience NUL byte at
data[size] (size excludes trailing NUL). Opaque binary outputs from se_program_artifact and se_session_save may contain embedded 0x00 bytes—always use exact size.
- Handles & Thread Safety:
se_program, se_session, and se_object are distinct nonzero uint64_t IDs never reused within a library instance. Concurrent C ABI calls on handles are synchronized internally; caller input pointers and output se_buffer structs must remain valid and unmutated for the duration of an active call.
Categorized Summary of All 27 Exported Functions
| Category |
Function |
Behavior & Output Contract |
| ABI & Buffer (2) | se_abi_version | Returns 1u for ABI v1 compatibility verification. |
| se_buffer_release | Frees engine-owned buffer memory and resets *buffer to {0}. |
| Program (6) | se_compile | Compiles story-engine/turn-source/1 UTF-8 JSON into a se_program handle plus diagnostics. |
| se_program_load | Validates and loads an opaque binary artifact (artifact, size) into a se_program handle. |
| se_program_artifact | Exports a compiled program as an opaque binary byte buffer (data + size). |
| se_program_inspect | Returns JSON catalog of fingerprint, types, entities/ancestors, states, snapshots, groups, uses, and turns. |
| se_program_retain | Publishes a distinct se_program handle requiring its own se_program_release. |
| se_program_release | Releases a se_program handle (live sessions retain program backing independently). |
| Session Lifecycle (6) | se_session_create | Creates an "idle" session initialized with default states and deterministic RNG seed. |
| se_session_clone | Creates an independent runtime copy (clearing external leases) for speculative branching or rollback. |
| se_session_save | Serializes session state into an opaque binary checkpoint byte buffer (data + size). |
| se_session_load | Restores a session from an opaque binary checkpoint verified against program. |
| se_session_retain | Publishes a distinct se_session handle referencing the same session. |
| se_session_release | Releases a se_session handle (defers teardown until in-flight calls complete). |
| Execution & State (6) | se_session_start | Configures entry turn and optional group bindings on an "idle" session, entering "ready". |
| se_session_advance | Executes one primary turn, or resolves a pending choice via answer_json and executes its follow-up turn. Choice answer_json accepts optional updates (external state batch assignments; internal state writes rejected with readonly_state) and optional bindings, evaluated atomically before option.next routing, prepended to result changes, and rolled back completely to awaiting_choice on any error. |
| se_session_advance_to_yield | Executes internal update/capture turns atomically up to max_steps until an observable turn or exit; supports the same atomic choice updates/bindings, prepended changes, and full rollback to awaiting_choice on error. |
| se_session_read | Atomically reads canonical values for an array of StateRef targets without advancing execution. |
| se_session_update | Applies an atomic host batch of assignments to "external" states when no choice is pending. |
| se_session_evaluate | Evaluates expression_id purely against current session state without side effects. |
| Snapshots & GC (3) | se_session_read_snapshot | Reads immutable values stored in a captured snapshot record ("<snapshot>#N"). |
| se_session_view | Returns current phase, next, pending_turn, request, and mutation version. |
| se_session_collect | Runs bounded session maintenance up to budget work units. |
| Object Leases & Global GC (4) | se_object_retain | Acquires an external se_object hold on a state, entity, or snapshot record ID. |
| se_object_read | Reads a retained state’s canonical JSON value (valid even after se_session_release). |
| se_object_release | Releases an external se_object handle. |
| se_collect_retired | Reclaims retired closed sessions up to budget work units. |
Exact C Prototypes (All 27 Symbols)
typedef uint64_t se_program;
typedef uint64_t se_session;
typedef uint64_t se_object;
typedef enum se_status {
SE_OK = 0,
SE_ERROR = 1,
SE_INVALID_ARGUMENT = 2,
SE_INVALID_HANDLE = 3,
SE_COMPILE_ERROR = 4,
SE_OUT_OF_MEMORY = 5
} se_status;
typedef struct se_buffer {
char *data;
size_t size;
} se_buffer;
#define SE_MAX_JSON_BYTES ((size_t)8 * 1024 * 1024)
#define SE_MAX_ARTIFACT_BYTES (SE_MAX_JSON_BYTES + (size_t)4096)
#define SE_MAX_CHECKPOINT_BYTES ((size_t)16 * 1024 * 1024)
/* 1–2: ABI Version & Buffer Ownership */
SE_API uint32_t se_abi_version(void);
SE_API void se_buffer_release(se_buffer *buffer);
/* 3–8: Program Compilation, Artifact I/O, Inspection & Lifetime */
SE_API se_status se_compile(const char *source_json, se_program *program, se_buffer *diagnostics);
SE_API se_status se_program_load(const void *artifact, size_t size, se_program *program, se_buffer *diagnostics);
SE_API se_status se_program_artifact(se_program program, se_buffer *artifact);
SE_API se_status se_program_inspect(se_program program, se_buffer *result);
SE_API se_status se_program_retain(se_program program, se_program *retained);
SE_API se_status se_program_release(se_program program);
/* 9–14: Session Creation, Cloning, Opaque Checkpoints & Lifetime */
SE_API se_status se_session_create(se_program program, uint64_t seed, se_session *session, se_buffer *result);
SE_API se_status se_session_clone(se_session source, se_session *cloned, se_buffer *result);
SE_API se_status se_session_save(se_session session, se_buffer *checkpoint);
SE_API se_status se_session_load(se_program program, const void *checkpoint, size_t size, se_session *session, se_buffer *result);
SE_API se_status se_session_retain(se_session session, se_session *retained);
SE_API se_status se_session_release(se_session session);
/* 15–23: Turn Stepping, State Access, Pure Evaluation, Snapshots & Maintenance */
SE_API se_status se_session_start(se_session session, const char *entry_json, se_buffer *result);
SE_API se_status se_session_advance(se_session session, const char *answer_json, se_buffer *result);
SE_API se_status se_session_advance_to_yield(se_session session, const char *answer_json, size_t max_steps, se_buffer *result);
SE_API se_status se_session_read(se_session session, const char *refs_json, se_buffer *result);
SE_API se_status se_session_update(se_session session, const char *assignments_json, const char *bindings_json, se_buffer *result);
SE_API se_status se_session_evaluate(se_session session, const char *expression_id, const char *bindings_json, se_buffer *result);
SE_API se_status se_session_read_snapshot(se_session session, const char *record_id, se_buffer *result);
SE_API se_status se_session_view(se_session session, se_buffer *result);
SE_API se_status se_session_collect(se_session session, size_t budget, se_buffer *result);
/* 24–27: External Object Leases & Retired Session Reclamation */
SE_API se_status se_object_retain(se_session session, const char *object_id, se_object *object, se_buffer *result);
SE_API se_status se_object_read(se_object object, se_buffer *result);
SE_API se_status se_object_release(se_object object);
SE_API se_status se_collect_retired(size_t budget, se_buffer *result);
05 · C ABI リファレンス (story_engine/story_engine.h)
Story Engine 共有ライブラリは C++ 例外が境界を越えない 全 27 個の C11 ABI 関数のみをエクスポートします。起動時に se_abi_version() == 1u を確認してバイナリの互換性を検証してください。
上限定数とステータスコード(se_status)
| シンボル / 定数 |
値 |
契約内容 |
se_abi_version() | 1u (ABI Version 1) | ロードされた共有ライブラリの C ABI バージョン番号。 |
SE_MAX_JSON_BYTES | 8,388,608 (8 MiB) | UTF-8 JSON およびテキスト入出力の最大バイト長(終端 NUL を除く)。 |
SE_MAX_ARTIFACT_BYTES | 8,392,704 (8 MiB + 4 KiB) | se_program_load が受け付ける不透明バイナリアーティファクトの最大サイズ。 |
SE_MAX_CHECKPOINT_BYTES | 16,777,216 (16 MiB) | se_session_load が受け付ける不透明バイナリチェックポイントの最大サイズ。 |
SE_OK | 0 | 成功。出力 se_buffer に結果 JSON またはバイナリデータが格納されます。 |
SE_ERROR | 1 | JSON 構文/スキーマ違反、invalid_phase、pending_choice、readonly_state、算術・範囲エラー(invalid_range 等)。 |
SE_INVALID_ARGUMENT | 2 | 必須ポインタが NULL、サイズ上限超過、非空の出力 se_buffer、max_steps == 0 等。 |
SE_INVALID_HANDLE | 3 | 0、解放済みハンドル、未知の ID、または異なる種類のハンドルを指定した場合。 |
SE_COMPILE_ERROR | 4 | 静的意味検証またはアーティファクト検証失敗(error.code == "compile_error"、diagnostics[] 同伴)。 |
SE_OUT_OF_MEMORY | 5 | メモリ確保失敗。出力バッファが {0} のままになる場合があるため必ず先に se_status を確認してください。 |
se_buffer 所有権とハンドルの並行安全性
- 出力バッファの
{0} 初期化: 出力用の se_buffer は呼び出し前に必ず {0} で初期化してください。空でないバッファを渡すと既存データを破壊せず即座に SE_INVALID_ARGUMENT を返します。
se_buffer_release による解放: 成功・失敗にかかわらず buf.data != NULL の場合は se_buffer_release(&buf) で解放してください(free() の直接呼び出しや構造体の浅いコピーによる二重解放は禁止)。解放後は {0} にリセットされます。
- JSON 出力と不透明バイナリ出力の扱い: JSON 出力は
data[size] に終端 '\0' が付与されます(size は終端を除く)。一方、se_program_artifact と se_session_save の出力は途中に 0x00 を含み得る不透明バイナリであるため、必ず size バイト数で処理してください。
- ハンドル寿命とスレッド安全性:
se_program・se_session・se_object は 0 以外の uint64_t であり再利用されません。C ABI 経由の並行呼び出しは内部で直列化されますが、呼び出し中の入力メモリおよび出力 se_buffer を別スレッドから変更・解放してはなりません。
全 27 公開関数カテゴリ別一覧
| カテゴリ |
関数名 |
動作と出力契約 |
| ABI・バッファ (2) | se_abi_version | ABI バージョン(1u)を返す。 |
| se_buffer_release | エンジン所有の se_buffer を解放し {0} にリセットする(冪等)。 |
| プログラム (6) | se_compile | story-engine/turn-source/1 JSON を検証・コンパイルし se_program と診断配列を返す。 |
| se_program_load | 不透明バイナリアーティファクト(artifact, size)を検証・ロードする。 |
| se_program_artifact | コンパイル済みプログラムを不透明バイナリバイトバッファ(data + size)へ書き出す。 |
| se_program_inspect | プログラム指紋・型・エンティティ階層・状態・スナップショット・グループ・ターンのカタログ JSON を返す。 |
| se_program_retain | 個別に解放可能な新しい se_program ハンドルを発行する。 |
| se_program_release | se_program ハンドルを解放する(稼働中セッションは内部参照を保持)。 |
| セッション管理 (6) | se_session_create | "idle" 状態のセッションを生成する(seed で重み付き乱数分岐を制御)。 |
| se_session_clone | 先読み実行や即時ロールバック用に独立したセッション複製を生成する(外部リースは引き継がない)。 |
| se_session_save | セッション状態を不透明バイナリチェックポイント(data + size)として直列化する。 |
| se_session_load | 元の program と照合検証した上で不透明バイナリチェックポイントからセッションを復元する。 |
| se_session_retain | 同一セッションを参照する別個の se_session ハンドルを発行する。 |
| se_session_release | se_session ハンドルを解放する。 |
| 進行・状態・評価 (6) | se_session_start | "idle" セッションの開始ターン/グループ束縛(entry_json)を設定し "ready" に遷移させる。 |
| se_session_advance | 主要ターンを 1 つ実行するか、answer_json で選択を確定して後続ターンを 1 つ実行する。選択応答は任意の updates(外部状態の一括更新、internal 状態への書き込みは readonly_state で拒否)および bindings を受け付け、option.next ルート解決前にアトミックに適用して結果の changes 先頭へ追加する(エラー発生時は awaiting_choice へ完全ロールバック)。 |
| se_session_advance_to_yield | 内部の update/capture を max_steps 予算内で連続実行し、最初の可視ターンまたは出口で返す。選択応答の updates/bindings 先行アトミック適用・changes 先頭記録・エラー時の awaiting_choice 完全ロールバック契約に同様に対応。 |
| se_session_read | 進行を伴わず StateRef 配列の正規化値マップをアトミックに読み取る。 |
| se_session_update | 選択待ちでない状態でホストから "external" 状態群を一括アトミック更新する。 |
| se_session_evaluate | 副作用なしで expression_id を現在のセッション状態に対して純粋評価する。 |
| スナップショット・回収 (3) | se_session_read_snapshot | capture ターンが記録した不変レコード("<snapshot>#N")を参照する。 |
| se_session_view | 現在の phase, next, pending_turn, request, version を取得する。 |
| se_session_collect | 稼働中セッションの不要オブジェクトを budget 上限内で回収する。 |
| 外部オブジェクト・退役回収 (4) | se_object_retain | 状態・エンティティ・スナップショットレコード ID の外部ハンドル(se_object)を保持する。 |
| se_object_read | 保持した状態 se_object の現在値を読み取る(セッション解放後も有効)。 |
| se_object_release | 外部 se_object ハンドルを解放する。 |
| se_collect_retired | クローズ済みセッションの物理リソースを budget 上限内で回収する。 |
C ヘッダ全関数プロトタイプ(全 27 シンボル)
typedef uint64_t se_program;
typedef uint64_t se_session;
typedef uint64_t se_object;
typedef enum se_status {
SE_OK = 0,
SE_ERROR = 1,
SE_INVALID_ARGUMENT = 2,
SE_INVALID_HANDLE = 3,
SE_COMPILE_ERROR = 4,
SE_OUT_OF_MEMORY = 5
} se_status;
typedef struct se_buffer {
char *data;
size_t size;
} se_buffer;
#define SE_MAX_JSON_BYTES ((size_t)8 * 1024 * 1024)
#define SE_MAX_ARTIFACT_BYTES (SE_MAX_JSON_BYTES + (size_t)4096)
#define SE_MAX_CHECKPOINT_BYTES ((size_t)16 * 1024 * 1024)
/* 1–2: ABI Version & Buffer Ownership */
SE_API uint32_t se_abi_version(void);
SE_API void se_buffer_release(se_buffer *buffer);
/* 3–8: Program Compilation, Artifact I/O, Inspection & Lifetime */
SE_API se_status se_compile(const char *source_json, se_program *program, se_buffer *diagnostics);
SE_API se_status se_program_load(const void *artifact, size_t size, se_program *program, se_buffer *diagnostics);
SE_API se_status se_program_artifact(se_program program, se_buffer *artifact);
SE_API se_status se_program_inspect(se_program program, se_buffer *result);
SE_API se_status se_program_retain(se_program program, se_program *retained);
SE_API se_status se_program_release(se_program program);
/* 9–14: Session Creation, Cloning, Opaque Checkpoints & Lifetime */
SE_API se_status se_session_create(se_program program, uint64_t seed, se_session *session, se_buffer *result);
SE_API se_status se_session_clone(se_session source, se_session *cloned, se_buffer *result);
SE_API se_status se_session_save(se_session session, se_buffer *checkpoint);
SE_API se_status se_session_load(se_program program, const void *checkpoint, size_t size, se_session *session, se_buffer *result);
SE_API se_status se_session_retain(se_session session, se_session *retained);
SE_API se_status se_session_release(se_session session);
/* 15–23: Turn Stepping, State Access, Pure Evaluation, Snapshots & Maintenance */
SE_API se_status se_session_start(se_session session, const char *entry_json, se_buffer *result);
SE_API se_status se_session_advance(se_session session, const char *answer_json, se_buffer *result);
SE_API se_status se_session_advance_to_yield(se_session session, const char *answer_json, size_t max_steps, se_buffer *result);
SE_API se_status se_session_read(se_session session, const char *refs_json, se_buffer *result);
SE_API se_status se_session_update(se_session session, const char *assignments_json, const char *bindings_json, se_buffer *result);
SE_API se_status se_session_evaluate(se_session session, const char *expression_id, const char *bindings_json, se_buffer *result);
SE_API se_status se_session_read_snapshot(se_session session, const char *record_id, se_buffer *result);
SE_API se_status se_session_view(se_session session, se_buffer *result);
SE_API se_status se_session_collect(se_session session, size_t budget, se_buffer *result);
/* 24–27: External Object Leases & Retired Session Reclamation */
SE_API se_status se_object_retain(se_session session, const char *object_id, se_object *object, se_buffer *result);
SE_API se_status se_object_read(se_object object, se_buffer *result);
SE_API se_status se_object_release(se_object object);
SE_API se_status se_collect_retired(size_t budget, se_buffer *result);
08 · C++ / C# (Unity) / Godot 엔진 연동
모든 호스트 언어 어댑터는 동일한 C ABI(story_engine.h) 위에서 동작하며, 다음 4가지 공통 원칙을 따릅니다: ① 길이 기반 문자열의 내부 NUL 바이트 사전 차단(embedded_nul), ② 64비트 핸들의 명시적 소유권 해제(RAII / IDisposable / RefCounted), ③ 64비트 십진 문자열 정수 파싱, ④ 프레임 여유 시점에 호스트가 직접 호출하는 collect / collect_retired.
1. C++17 & Unreal Engine
C++17 호스트에서는 se_program, se_session, se_object, se_buffer를 이동 전용(Move-only) RAII 래퍼로 감싸 예외 안전성을 확보합니다. Unreal Engine 플러그인 모듈에서는 ThirdParty/StoryEngine/include/story_engine와 플랫폼별 라이브러리(Mac, Linux, Win64)를 Build.cs에 등록하며, 스토리 상태를 UObject GC에 종속시키지 않고 게임 서브시스템/컴포넌트 멤버 수명으로 관리합니다.
// C++17 RAII 패턴 예시 (story_engine.h 직접 래핑)
auto prog = story_engine::binding::Program::load(artifact_bytes.data(), artifact_bytes.size());
auto sess = prog.session(/*seed=*/42);
sess.start(R"({"turn":"record_opening"})");
// 내부 update/capture를 통과해 첫 가시 턴까지 진행
auto step_buf = sess.advance_to_yield(nullptr, /*max_steps=*/64);
std::string step_json = step_buf.text();
// 추론적 분기 미리보기용 O(1) 복제 및 불투명 체크포인트 저장
auto preview = sess.clone();
auto saved_checkpoint = sess.save(); // saved_checkpoint.data(), saved_checkpoint.size()
2. Unity 및 매니지드 C# (P/Invoke + SafeHandle)
Unity 및 .NET 호스트에서는 네이티브 바이너리(libstory_engine.dylib, libstory_engine.so, story_engine.dll)를 네이티브 플러그인 경로에 배치하고 CallingConvention.Cdecl로 P/Invoke합니다. 32비트 호스트에서도 핸들이 잘리지 않도록 네이티브 핸들은 IntPtr 대신 항상 ulong으로 유지하며, SafeHandle(DangerousAddRef / DangerousRelease)과 IDisposable을 함께 구현해 활성 호출 중 동시 Dispose 경쟁을 방지합니다.
using StoryEngine;
using var program = Program.Load(artifactBytes);
using var session = program.CreateSession(seed: 42UL);
using (session.Start("{\"turn\":\"record_opening\"}")) { }
using (Result step = session.AdvanceToYield(answer: null, maxSteps: 64))
{
string json = step.Json; // UTF-8 JSON envelope
// 선택 대기 시: session.AdvanceToYield("{\"request\":\"choice#1\",\"option\":\"pay\"}", 64)
}
// 씬 전환 등 호스트가 선택한 시점에 은퇴 세션 정리
using (Result gc = Collector.CollectRetired(budget: 128)) { }
3. Godot 4 (GDExtension / GDScript)
코어 공유 라이브러리의 C ABI 자체는 Godot 전용 심볼이 아니므로, godot-cpp 기반 GDExtension 래퍼(RefCounted 파생 StoryRuntime 및 StoryObject)를 통해 GDScript에 노출합니다. Godot 내장 JSON.parse_string은 JSON 숫자를 부동소수점으로 변환하므로, 정규화 int 값의 십진 문자열에 GDScript int(val["value"])를 호출해 64비트 부호 있는 정수 전 범위를 무손실로 복원하십시오.
var runtime = ClassDB.instantiate("StoryRuntime")
assert(JSON.parse_string(runtime.load_artifact(artifact_bytes))["ok"])
assert(JSON.parse_string(runtime.create_session(42))["ok"])
assert(JSON.parse_string(runtime.start('{"turn":"say"}'))["ok"])
var step = JSON.parse_string(runtime.advance())["result"]
if step["phase"] == "awaiting_choice":
var answer = JSON.stringify({"request": step["request"], "option": step["choices"][0]["id"]})
step = JSON.parse_string(runtime.advance(answer))["result"]
# 64비트 정수 무손실 읽기 (문자열 -> GDScript 64-bit int)
var coins_entry = JSON.parse_string(runtime.read('["coins"]'))["result"]["coins"]
var coins: int = int(coins_entry["value"])
runtime.close()
runtime.collect_retired(100)
08 · C++ / C# (Unity) / Godot Integration
All host language adapters layer cleanly atop the same C11 ABI (story_engine.h) and enforce four shared invariants: (1) reject embedded NUL bytes in length-aware host strings before crossing into C strings (embedded_nul), (2) manage 64-bit handles deterministically (RAII, IDisposable, or RefCounted), (3) parse canonical 64-bit integers from decimal strings, and (4) invoke collect / collect_retired explicitly at host-chosen frame boundaries.
1. C++17 & Unreal Engine
Wrap se_program, se_session, se_object, and se_buffer in move-only C++17 RAII owners. For Unreal Engine, place headers under ThirdParty/StoryEngine/include/story_engine and target binaries under ThirdParty/StoryEngine/lib/{Mac,Linux,Win64} in your module’s Build.cs. Store RAII owners inside subsystem or component members rather than wrapping individual story states as UObject instances.
// Move-only C++17 RAII usage over story_engine.h
auto prog = story_engine::binding::Program::load(artifact_bytes.data(), artifact_bytes.size());
auto sess = prog.session(/*seed=*/42);
sess.start(R"({"turn":"record_opening"})");
// Step past internal updates/captures to first observable turn
auto step_buf = sess.advance_to_yield(nullptr, /*max_steps=*/64);
std::string step_json = step_buf.text();
// Speculative branch clone and opaque binary checkpoint export
auto preview = sess.clone();
auto saved_checkpoint = sess.save(); // saved_checkpoint.data(), saved_checkpoint.size()
2. Unity & Managed C# (P/Invoke + SafeHandle)
Place the platform native binary (libstory_engine.dylib, libstory_engine.so, or story_engine.dll) in Unity’s Plugins folder and bind with [DllImport("story_engine", CallingConvention = CallingConvention.Cdecl)]. Keep native handles typed as ulong (never truncate into 32-bit IntPtr), validate UTF-16 surrogates and embedded NULs on managed strings, and pin calls with SafeHandle.DangerousAddRef / DangerousRelease alongside explicit Dispose().
using StoryEngine;
using var program = Program.Load(artifactBytes);
using var session = program.CreateSession(seed: 42UL);
using (session.Start("{\"turn\":\"record_opening\"}")) { }
using (Result step = session.AdvanceToYield(answer: null, maxSteps: 64))
{
string json = step.Json; // UTF-8 JSON envelope
// When awaiting choice: session.AdvanceToYield("{\"request\":\"choice#1\",\"option\":\"pay\"}", 64)
}
// Reclaim retired sessions during scene transitions or low-load frames
using (Result gc = Collector.CollectRetired(budget: 128)) { }
3. Godot 4 (GDExtension / GDScript)
Because the core C ABI is engine-agnostic, Godot 4 projects bind via a godot-cpp GDExtension exposing RefCounted wrappers (StoryRuntime and StoryObject). Godot’s built-in JSON.parse_string converts numeric tokens to floating-point, so pass canonical integer string digits directly to GDScript’s int(...) to preserve full signed 64-bit precision.
var runtime = ClassDB.instantiate("StoryRuntime")
assert(JSON.parse_string(runtime.load_artifact(artifact_bytes))["ok"])
assert(JSON.parse_string(runtime.create_session(42))["ok"])
assert(JSON.parse_string(runtime.start('{"turn":"say"}'))["ok"])
var step = JSON.parse_string(runtime.advance())["result"]
if step["phase"] == "awaiting_choice":
var answer = JSON.stringify({"request": step["request"], "option": step["choices"][0]["id"]})
step = JSON.parse_string(runtime.advance(answer))["result"]
# Lossless signed 64-bit integer conversion from canonical decimal string
var coins_entry = JSON.parse_string(runtime.read('["coins"]'))["result"]["coins"]
var coins: int = int(coins_entry["value"])
runtime.close()
runtime.collect_retired(100)
08 · C++ / C# (Unity) / Godot 統合
すべてのホスト言語アダプタは共通の C11 ABI(story_engine.h)上に構築され、4 つの原則を共有します:① 長さ付き文字列内の NUL バイト事前拒否(embedded_nul)、② 64 ビットハンドルの明示的な寿命管理(RAII / IDisposable / RefCounted)、③ 10 進文字列からの 64 ビット整数パース、④ ホスト側が任意のタイミングで呼び出す collect / collect_retired。
1. C++17 & Unreal Engine
se_program・se_session・se_object・se_buffer をムーブ専用の C++17 RAII クラスでラップします。Unreal Engine では ThirdParty/StoryEngine/include/story_engine と各 OS 用バイナリ(Mac, Linux, Win64)を Build.cs に登録し、ストーリー状態を UObject 化せずサブシステムやコンポーネントのメンバ寿命で管理します。
// story_engine.h 上のムーブ専用 C++17 RAII パターン
auto prog = story_engine::binding::Program::load(artifact_bytes.data(), artifact_bytes.size());
auto sess = prog.session(/*seed=*/42);
sess.start(R"({"turn":"record_opening"})");
// 内部の update / capture を通過して最初の可視ターンまで進行
auto step_buf = sess.advance_to_yield(nullptr, /*max_steps=*/64);
std::string step_json = step_buf.text();
// 分岐プレビュー用の複製と不透明チェックポイントの保存
auto preview = sess.clone();
auto saved_checkpoint = sess.save(); // saved_checkpoint.data(), saved_checkpoint.size()
2. Unity およびマネージド C#(P/Invoke + SafeHandle)
ネイティブプラグイン(libstory_engine.dylib, libstory_engine.so, story_engine.dll)を配置し、CallingConvention.Cdecl で P/Invoke します。32 ビット環境での切り捨てを防ぐためネイティブ ID は常に ulong で保持し、SafeHandle(DangerousAddRef / DangerousRelease)と IDisposable を併用して呼び出し中の並行 Dispose 競合を防ぎます。
using StoryEngine;
using var program = Program.Load(artifactBytes);
using var session = program.CreateSession(seed: 42UL);
using (session.Start("{\"turn\":\"record_opening\"}")) { }
using (Result step = session.AdvanceToYield(answer: null, maxSteps: 64))
{
string json = step.Json; // UTF-8 JSON エンベロープ
// 選択待ち時: session.AdvanceToYield("{\"request\":\"choice#1\",\"option\":\"pay\"}", 64)
}
// シーン遷移時など任意のタイミングで退役セッションを回収
using (Result gc = Collector.CollectRetired(budget: 128)) { }
3. Godot 4(GDExtension / GDScript)
コアの C ABI 自体は GDExtension シンボルではないため、godot-cpp ベースの RefCounted ラッパー(StoryRuntime および StoryObject)を経由して GDScript から呼び出します。Godot 標準の JSON.parse_string は数値を浮動小数点に変換するため、正規化 int の 10 進文字列に int(...) を適用することで符号付き 64 ビット精度を維持できます。
var runtime = ClassDB.instantiate("StoryRuntime")
assert(JSON.parse_string(runtime.load_artifact(artifact_bytes))["ok"])
assert(JSON.parse_string(runtime.create_session(42))["ok"])
assert(JSON.parse_string(runtime.start('{"turn":"say"}'))["ok"])
var step = JSON.parse_string(runtime.advance())["result"]
if step["phase"] == "awaiting_choice":
var answer = JSON.stringify({"request": step["request"], "option": step["choices"][0]["id"]})
step = JSON.parse_string(runtime.advance(answer))["result"]
# 正規化 10 進文字列から GDScript の 64 ビット int へ無損失変換
var coins_entry = JSON.parse_string(runtime.read('["coins"]'))["result"]["coins"]
var coins: int = int(coins_entry["value"])
runtime.close()
runtime.collect_retired(100)
09 · Story Editor 및 원격 MCP 레퍼런스
602 Story Editor v0.1.2(Story Engine v1.2.0 네이티브 런타임 번들 내장)는 외부 Python/Node.js 설치 없이 실행되는 독립 실행형 시각 그래프 편집·시뮬레이션·에이전트 서버입니다. 웹 편집 인터페이스는 가로 1440px 이상의 데스크톱 브라우저 환경을 기준으로 설계되었습니다.
1. 계정 초기화 및 HTTPS 리버스 프록시 기동
계정 인증 DB(--account-database)와 프로젝트 DB(--database)는 아카이브 외부의 서로 다른 비-심볼릭 링크 절대 경로에 배치해야 합니다. 백엔드 리스너는 루프백(127.0.0.1 또는 ::1)에서만 동작하므로 앞단에 TLS를 종료하는 Caddy 또는 Nginx를 배치하고 --trust-https-proxy와 함께 실행합니다.
# 1) 초기 관리자 계정 생성 (24시간 유효 임시 비밀번호 발급, 첫 로그인 시 /change-password 필수)
./story-editor account \
--database /var/lib/story-editor/auth.sqlite3 \
--username writer \
--owner-email writer@example.com
# 2) 루프백 백엔드 기동 (외부 공개 HTTPS 오리진 필수)
./story-editor serve \
--public-url https://story.example \
--account-database /var/lib/story-editor/auth.sqlite3 \
--database /var/lib/story-editor/editor.sqlite3 \
--host 127.0.0.1 \
--port 8781 \
--trust-https-proxy
# Caddyfile 예시 (Host 및 X-Forwarded-Proto 자동 전달 + 즉시 스트림 플러시)
story.example {
reverse_proxy 127.0.0.1:8781
}
# Nginx 핵심 location 설정 (Host 유지 및 에이전트 NDJSON 스트리밍 버퍼링 해제 필수)
location / {
proxy_pass http://127.0.0.1:8781;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_buffering off;
}
2. 시각적 그래프 작성, 중간 턴 시뮬레이션 및 story-agent
- 그래프 작성 및 중간 장면 점프 시뮬레이션: 6종 주 작업 턴과 6종 보조 계산·분기 노드(계산 노드의
min(a, b), max(a, b), clamp(value, lo, hi) 지원; lo > hi이면 invalid_range 거부), 재사용 묶음(Group)을 캔버스에서 편집하고 내장 네이티브 엔진으로 즉시 컴파일합니다. 시뮬레이션 드롭다운의 지정 턴에서 중간 시작으로 임의의 턴(그룹 결속 포함)과 초기 상태값을 지정해 즉시 검증할 수 있으며, 선택지 진행 시 answer.updates를 통해 외부 상태 갱신과 후속 분기 실행을 원자적으로 함께 수행할 수 있습니다(실패 시 선택 대기 상태 유지).
- 내장
story-agent 협업 모드: 세션별로 변경안을 미리보기로 스테이징해 검토 후 반영하는 approval 모드와 즉시 반영하는 full_access 모드를 전환할 수 있습니다. 외부 모델 연결은 STORY_AGENT_PROVIDER=openai + STORY_AGENT_API_KEY(전 플랫폼 공통, Windows 필수) 또는 POSIX 전용 STORY_AGENT_PROVIDER=openai-codex + STORY_AGENT_AUTH_DIR(./agent/story-agent --login)로 구성합니다.
3. 원격 MCP 엔드포인트 (POST /mcp) 및 6자리 해시 동시성 제어
에디터 하단 에이전트 창의 MCP 설정 모달(또는 POST /api/turn/mcp/tokens)에서 발급받은 Bearer 토큰과 세션 ID로 외부 에이전트를 연결합니다. 모든 POST /mcp 요청(최대 본문 256 KiB, 토큰당 분당 120회 제한)에는 Authorization: Bearer <token>과 Story-Editor-Session: <session_id> 헤더가 필수입니다. Modern Stateless 프로토콜(2026-07-28: server/discover → tools/list → tools/call)과 Legacy(2025-11-25: initialize)를 모두 지원합니다.
| MCP 도구 |
주요 입력 인자 |
동작 및 6자리 16진수 해시 계약 |
inspect |
scope: "view" | "projects" | "project" | "manuscript", projectId, collection, ids, query, related |
열린 화면, 프로젝트 목록, 단일 프로젝트 요약, 또는 원고 객체와 연결 포트를 조회하며 6자리 소문자 16진수 metadataHash 및 각 객체의 6자리 hash를 반환합니다. |
edit |
scope: "project" (create | select | update) 또는 scope: "manuscript" (operations[]: create | update | delete | set) |
update / delete / set 시 대상의 현재 6자리 expectedHash를 검증합니다. 권한에 따라 status: "applied" 또는 승인 대기 "pending"을 반환합니다. |
analyze |
kind: "validation", 선택적 projectId |
내장 네이티브 Story Engine 컴파일러로 현재 원고(또는 대기 중인 제안 기준 상태)를 정적 검증하여 success와 diagnostics[]를 반환합니다. |
낙관적 동시성 충돌 복구 (project_conflict): 다른 사용자나 세션이 먼저 원고를 수정해 6자리 해시(예: "5900a9")가 변경된 경우, edit는 JSON-RPC 치명 오류 대신 복구 가능한 도구 결과 오류(result.isError: true, structuredContent.error.code: "project_conflict", requiresInspect: true)를 반환합니다. 클라이언트는 inspect를 다시 호출해 최신 6자리 hash / metadataHash를 확인한 뒤 패치를 재적용하면 됩니다.
# Modern Stateless MCP (2026-07-28) tools/call 호출 예시
curl -sS -X POST https://story.example/mcp \
-H "Authorization: Bearer $STORY_EDITOR_MCP_TOKEN" \
-H "Story-Editor-Session: $STORY_EDITOR_SESSION_ID" \
-H "MCP-Protocol-Version: 2026-07-28" \
-H "Mcp-Method: tools/call" \
-H "Mcp-Name: inspect" \
-H "Accept: application/json, text/event-stream" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "inspect",
"arguments": { "scope": "manuscript", "collection": "turns", "limit": 16 },
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}'
09 · Story Editor & Remote MCP
602 Story Editor v0.1.2 (bundling the native Story Engine v1.2.0 runtime) is a standalone visual graph authoring, runtime simulation, and agent collaboration server requiring zero pre-installed Python or Node.js runtimes. Its web workspace targets desktop viewports at 1440px width or wider.
1. Account Initialization & HTTPS Reverse Proxy Deployment
Store --account-database and --database at distinct, non-symlink absolute paths outside the extracted archive directory. Because story-editor serve binds strictly to loopback HTTP (127.0.0.1 or ::1), place a TLS-terminating reverse proxy (Caddy or Nginx) in front and pass --trust-https-proxy.
# 1) Initialize operator account (24h temporary password; forces /change-password on first login)
./story-editor account \
--database /var/lib/story-editor/auth.sqlite3 \
--username writer \
--owner-email writer@example.com
# 2) Launch loopback server behind your public HTTPS origin
./story-editor serve \
--public-url https://story.example \
--account-database /var/lib/story-editor/auth.sqlite3 \
--database /var/lib/story-editor/editor.sqlite3 \
--host 127.0.0.1 \
--port 8781 \
--trust-https-proxy
# Caddyfile (automatically preserves Host, sets X-Forwarded-Proto, and flushes streams)
story.example {
reverse_proxy 127.0.0.1:8781
}
# Nginx location block (preserve Host and disable proxy_buffering for NDJSON agent streams)
location / {
proxy_pass http://127.0.0.1:8781;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_buffering off;
}
2. Visual Graph Authoring, Arbitrary-Turn Simulation & story-agent
- Graph Editing & Scene-Jump Simulation: Author nominal types, entity hierarchies, snapshots, 6 primary turn kinds, 6 pure/routing auxiliary nodes (including calculation node operators
min(a, b), max(a, b), and clamp(value, lo, hi), rejecting lo > hi with invalid_range), and reusable parameterized groups on the canvas, backed by live native compilation. Use Start from Selected Turn in the Simulation dropdown to jump straight into any mid-story scene with custom or randomized test states, and pass atomic external state mutations via answer.updates during choice advances (rolling back to awaiting choice on failure).
story-agent Proposal Workflow: Switch per session between approval mode (stages diff proposals for human review before commit) and full_access mode (applies edits immediately). Configure external LLM providers via STORY_AGENT_PROVIDER=openai + STORY_AGENT_API_KEY (all platforms; required on Windows) or POSIX OAuth via STORY_AGENT_PROVIDER=openai-codex + STORY_AGENT_AUTH_DIR (./agent/story-agent --login).
3. Remote MCP (POST /mcp) & 6-Hex Optimistic Concurrency
Mint scoped Bearer tokens and session IDs from the in-app MCP modal (or POST /api/turn/mcp/tokens using a logged-in __Host-story_session browser cookie). Every POST /mcp request (256 KiB max body; 120 requests / 60s per token) requires both Authorization: Bearer <token> and Story-Editor-Session: <session_id>. Both Modern Stateless MCP (2026-07-28: server/discover → tools/list → tools/call) and Legacy MCP (2025-11-25: initialize) are supported.
| MCP Tool |
Key Arguments |
Capabilities & 6-Hex Concurrency Contract |
inspect |
scope: "view" | "projects" | "project" | "manuscript", projectId, collection, ids, query, related |
Queries active workspace state, project list, project counts, or manuscript nodes/edges, returning 6-character lowercase hex metadataHash and per-object hash values. |
edit |
scope: "project" (create | select | update) or scope: "manuscript" (operations[]: create | update | delete | set) |
Verifies 6-hex expectedHash on every update, delete, and set operation; commits (status: "applied") or stages a proposal (status: "pending") depending on session/token permission mode. |
analyze |
kind: "validation", optional projectId |
Runs the bundled native Story Engine compiler against the manuscript, returning success and structured diagnostics[]. |
Optimistic Concurrency Recovery (project_conflict): When a 6-hex expectedHash (such as "5900a9") no longer matches server state due to concurrent edits, edit returns HTTP 200 with a recoverable tool domain error (result.isError: true, structuredContent.error.code: "project_conflict", requiresInspect: true). Re-run inspect to fetch the updated 6-hex hash and retry.
# Modern Stateless MCP (2026-07-28) tools/call example
curl -sS -X POST https://story.example/mcp \
-H "Authorization: Bearer $STORY_EDITOR_MCP_TOKEN" \
-H "Story-Editor-Session: $STORY_EDITOR_SESSION_ID" \
-H "MCP-Protocol-Version: 2026-07-28" \
-H "Mcp-Method: tools/call" \
-H "Mcp-Name: inspect" \
-H "Accept: application/json, text/event-stream" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "inspect",
"arguments": { "scope": "manuscript", "collection": "turns", "limit": 16 },
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}'
09 · Story Editor とリモート MCP
602 Story Editor v0.1.2(ネイティブ Story Engine v1.2.0 ランタイムを同梱)は、Python や Node.js の事前インストール不要で動作するスタンドアロン型のビジュアルグラフ編集・シミュレーション・エージェント連携サーバーです。Web UI は横幅 1440px 以上のデスクトップブラウザ環境を前提としています。
1. アカウント初期化と HTTPS リバースプロキシ構成
認証 DB(--account-database)とプロジェクト DB(--database)には、アーカイブ外の互いに異なる非シンボリックリンクの絶対パスを指定してください。バックエンドはループバック(127.0.0.1 または ::1)のみで待ち受けるため、前段に TLS 終端を行う Caddy または Nginx を配置し --trust-https-proxy を付与して起動します。
# 1) 管理者アカウント初期化(一時パスワードは24時間有効、初回ログイン時に /change-password 必須)
./story-editor account \
--database /var/lib/story-editor/auth.sqlite3 \
--username writer \
--owner-email writer@example.com
# 2) ループバック HTTP サーバー起動(公開 URL は https:// 必須)
./story-editor serve \
--public-url https://story.example \
--account-database /var/lib/story-editor/auth.sqlite3 \
--database /var/lib/story-editor/editor.sqlite3 \
--host 127.0.0.1 \
--port 8781 \
--trust-https-proxy
# Caddyfile 設定例(Host と X-Forwarded-Proto の自動転送・即時ストリームフラッシュ)
story.example {
reverse_proxy 127.0.0.1:8781
}
# Nginx location 設定例(Host 維持と NDJSON ストリーム向け proxy_buffering 無効化が必須)
location / {
proxy_pass http://127.0.0.1:8781;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_buffering off;
}
2. ビジュアルグラフ編集・途中ターンからのシミュレーション・story-agent
- グラフ編集と途中シーンジャンプ検証: 6 種の主要ターン・6 種の補助ノード(計算ノードの
min(a, b)・max(a, b)・clamp(value, lo, hi) に対応し、lo > hi は invalid_range で拒否)・再利用グループをキャンバス上で編集し、内蔵ネイティブエンジンで即座にコンパイルできます。シミュレーションメニューの指定ターンから途中開始を使うと任意のターンと初期状態値で即座にテストでき、選択肢進行時には answer.updates により外部状態更新と後続実行をアトミックに処理できます(失敗時は選択待ちを維持)。
- 内蔵
story-agent 提案ワークフロー: 変更案をプレビューして承認後に反映する approval モードと即時反映する full_access モードを切り替え可能です。外部 AI プロバイダは STORY_AGENT_PROVIDER=openai + STORY_AGENT_API_KEY(全 OS 共通・Windows 必須)、または POSIX 環境での STORY_AGENT_PROVIDER=openai-codex + STORY_AGENT_AUTH_DIR(./agent/story-agent --login)で設定します。
3. リモート MCP(POST /mcp)と 6 桁ハッシュ楽観的ロック
エディタ内の MCP 設定モーダル(または POST /api/turn/mcp/tokens)で発行した Bearer トークンとセッション ID を用いて外部クライアントから接続します。すべての POST /mcp リクエスト(最大 256 KiB、トークンあたり毎分 120 回制限)には Authorization: Bearer <token> と Story-Editor-Session: <session_id> ヘッダが必須です。Modern Stateless フロー(2026-07-28:server/discover → tools/list → tools/call)および Legacy(2025-11-25:initialize)の両方に対応しています。
| MCP ツール |
主な入力引数 |
機能と 6 桁 16 進ハッシュ契約 |
inspect |
scope: "view" | "projects" | "project" | "manuscript", projectId, collection, ids, query, related |
画面状態・プロジェクト一覧・単一プロジェクト要約・原稿オブジェクトと接続ポートを取得し、6 桁小文字 16 進の metadataHash および各オブジェクトの hash を返します。 |
edit |
scope: "project"(create | select | update)または scope: "manuscript"(operations[]: create | update | delete | set) |
update / delete / set 実行時に現在の 6 桁 expectedHash を照合します。権限モードに応じ status: "applied" または承認待ち "pending" を返します。 |
analyze |
kind: "validation"、任意の projectId |
内蔵ネイティブ Story Engine コンパイラで原稿を静的検証し、success と diagnostics[] を返します。 |
ハッシュ競合からの復旧(project_conflict): 並行編集により 6 桁の expectedHash(例:"5900a9")が最新状態と一致しない場合、edit は回復可能なドメインエラー(result.isError: true、structuredContent.error.code: "project_conflict"、requiresInspect: true)を返します。再度 inspect を呼んで最新の 6 桁ハッシュを取得してから再試行してください。
# Modern Stateless MCP (2026-07-28) tools/call リクエスト例
curl -sS -X POST https://story.example/mcp \
-H "Authorization: Bearer $STORY_EDITOR_MCP_TOKEN" \
-H "Story-Editor-Session: $STORY_EDITOR_SESSION_ID" \
-H "MCP-Protocol-Version: 2026-07-28" \
-H "Mcp-Method: tools/call" \
-H "Mcp-Name: inspect" \
-H "Accept: application/json, text/event-stream" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "inspect",
"arguments": { "scope": "manuscript", "collection": "turns", "limit": 16 },
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}'