- JavaScript 67.2%
- Python 31%
- Shell 1.8%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Measured on Yggdrasil: loading by repo id leaves the model's _name_or_path equal to the Llama repo id, so prepare_for_tokenization wraps the text in the chat header and the vector matches assembly A (cosine 1.0, 0.909 to the reference B). Loading from the snapshot paths reproduces the reference. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
| docs | ||
| scripts | ||
| vendor | ||
| .gitignore | ||
| .gitmodules | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
Vili
Vili is the GameCult Persona animation daemon.
It owns the bridge from Persona performance intent to body-motion generation. Its backend is Kimodo, run by Vili's own daemon as a CPU-only Python process tree on Linux. Admission to Idunn on Yggdrasil is the next cut; until then nothing supervises Vili.
Front Door
- Value: a supervised path from a text prompt to generated body motion. The daemon spawns one resident Kimodo worker, caps its threads, and returns motion artifacts (
motion.npz, optional BVH) for a job. - Problem: Personas and games need generated motion, but cold per-request model loads cost minutes. One daemon owns job identity, worker lifecycle, the thread budget and artifact references, so callers touch no model state.
- Entry points:
scripts/vili-daemon.mjs(the daemon;npm start,npm run health,npm test),scripts/kimodo-resident-worker.py(the worker it spawns),scripts/kimodo-text-encode.py(the one-shot encoder child),POST /motion/generate(see Motion Generation),docs/verse-service-contract.mdanddocs/motion-pipeline-target.md. - Live: the process tree below, run by hand:
POST /motion/generate,GET /health,GET /operator-state, and the CultCache service store. It producesmotion.npzand optional BVH on the SOMA skeleton; there is no FBX export. - Planned: admission to Idunn on Yggdrasil with a locked virtual environment, then FBX export and a text-prompt-to-Unity-clip path with no hand steps (
docs/motion-pipeline-target.md). The Raven instance still runs a retired branch until the admission cut tears it down.
Authority
- Vili owns animation job intake, the worker process lifecycle, and generated motion artifact references.
- Personas and Weksa may propose performance intent, gesture prompts, and constraints.
- Kimodo owns motion synthesis.
- Future renderers own retargeting, playback, facial performance, camera, and lip sync.
- Odin discovers Vili; Odin does not own Vili's state.
- Operator surfaces wait for Thing 2; today Vili publishes typed state and plain HTTP.
Local Runtime
One Linux CPU process tree:
vili-daemon.mjs job intake, worker lifecycle, thread cap, nice 10
kimodo-resident-worker.py resident Kimodo model, one generation at a time
kimodo-text-encode.py one child per request with cache misses, assembly B on CPU
Setup: git submodule update --init; in vendor/CultLib, npm ci --ignore-scripts and
npm run build --workspace=packages/cultcache-ts; then npm ci here. The worker
needs a Python environment with Kimodo's pins and Kimodo itself importable, and a
Hugging Face cache (<models root>/hub) holding the Llama 3 base, both LLM2Vec
adapters and the Kimodo checkpoint. Everything runs offline.
node scripts/vili-daemon.mjs --python <python> --models-root <hf home> --kimodo-commit <sha> --python-env <env id> [--threads 8]
Flags, each with an environment twin: --host (VILI_DAEMON_HOST, 127.0.0.1),
--port (VILI_DAEMON_PORT, 8824), --state-root (VILI_STATE_ROOT, .vili),
--python (VILI_PYTHON), --worker-entry (VILI_WORKER_ENTRY, the test seam),
--models-root (VILI_MODELS_ROOT), --threads (VILI_THREADS, 1 to 16, default
8), --kimodo-commit (VILI_KIMODO_COMMIT), --python-env (VILI_PYTHON_ENV, the
environment's identity). A generate request without a models root, Kimodo commit
and Python environment id gets 503 worker-unconfigured before anything spawns.
The daemon starts the worker on the first job, on a port the worker binds itself
and reports in its ready line, and respawns it after an exit. The worker runs at
nice 10 with torch limited to --threads (also OMP_NUM_THREADS and
MKL_NUM_THREADS), so a shared host keeps its other services. Its log is
<state>/logs/worker.log. SIGTERM stops the whole tree.
POST /motion/generate refuses any POST that carries an Origin or is not
application/json with 403 cross-site; no route answers a preflight.
Vili writes daemon-owned state to .vili/vili.service.cc: operator state, the
command boundary and the transport profile. Jobs are .vili/jobs/<jobId>.json,
artifacts .vili/artifacts/<jobId>/.
Routes: GET /health, GET /operator-state, POST /motion/generate.
Motion Generation
POST /motion/generate exposes Kimodo's generation surface through the
resident worker. Vili owns job identity, persistence, timeout handling, worker
lifecycle, and the output stem; Kimodo owns synthesis and model-specific export
formats.
Minimal request:
{
"prompt": "a person gives a small friendly wave",
"duration": 1,
"diffusionSteps": 20,
"bvh": true
}
Batch requests can use Kimodo-style metadata:
{
"meta": {
"texts": [
"a person gives a small friendly wave",
"a person takes one careful step"
],
"durations": [0.5, 0.5],
"num_samples": 1,
"diffusion_steps": 20
},
"numTransitionFrames": 3,
"cfgType": "regular",
"cfgWeight": [1.5],
"seed": 6101,
"bvh": true,
"bvhStandardTpose": true,
"saveExampleDir": true
}
Supported request fields:
prompt/utteranceplusduration/durationSecondstextsplusdurations, or a fullmetaobjectinputFolder/input_folderfor a Kimodo input folder on the worker host withmeta.jsonand optionalconstraints.jsonmodel; changing the requested model restarts the single resident workerdiffusionSteps/diffusion_stepsnumSamples/num_samplesnumTransitionFrames/num_transition_framesconstraintsas an object/list, orconstraints,constraintsPath, orconstraints_pathas a path on the worker hostseedcfg,cfgType/cfg_type,cfgWeight/cfg_weightbvh,bvhStandardTpose/bvh_standard_tposenoPostprocess/no_postprocesssaveExampleDir/save_example_dirtimeoutMs,workerTimeoutMs
Vili intentionally does not accept an arbitrary Kimodo output path. Generated
artifacts are written under .vili/artifacts/{jobId}/ and referenced from the
job record. Kimodo-SOMA-RP-v1.1 produces NPZ and optional BVH output,
Kimodo-SMPLX-RP-v1.1 also exports AMASS NPZ, and Kimodo-G1-RP-v1.1 also
exports CSV when that model is resident.
Current Runtime State
Kimodo's 8B text encoder (LLM2Vec, Llama 3) never lives in the worker. The
worker answers Kimodo's encoder call from a CultCache store of float32 vectors,
<state>/embeddings/text-embeddings.cc (gamecult.vili.text_embedding.v0, through
the cultcache-py in vendor/CultLib). A vector's key is
sha256(encoderIdentity + newline + text). The identity names the three Hugging
Face snapshot revisions, the encoder assembly, the device, dtype, the torch,
transformers, peft and accelerate versions, the Kimodo commit and the sha256 of
kimodo-text-encode.py, so a changed input misses instead of serving a stale
vector. Thread count is provenance, recorded in the worker identity, not identity.
The encoder is assembly B: the Llama 3 base with the MNTP adapter merged, then the
supervised adapter, in bfloat16 on CPU. Kimodo's own loader never applies the MNTP
weights under the pinned transformers; the A/B comparison is in
docs/research/vili-on-yggdrasil-cpu-2026-10.md, which also holds the measured
timings. Kimodo normalizes the prompt before encoding (A person shrugs sadly.),
and the cache keys on that text. A request's misses go to one transient child that
loads the encoder, encodes, writes its answer and exits, so the OS reclaims its
RAM; hits never touch it. Each job carries the encoder record (hits, misses,
seconds, vector hashes) and the worker identity. The cache starts empty on a new
host: vectors from another assembly or device are never copied in.