ipin-vr/ipin_vr/export.py
Oskar Kapala 921a85d30b Add export subcommand and Unity PoC-1 scaffold (ONDEVICE §4)
Python (ipin_vr/export.py):
- generate_bank(): levels 1–2 enumerated exhaustively (120/720 combos),
  level 3 random with dedup; all deduplicated per-level
- run_export(): CLI --per-level / --out / --seed flags per ONDEVICE §4.1
- cli.py routes "export" subcommand before session args (backward-compat)
- 14 new tests: format, dedup for all 3 levels, space caps (L1=120, L2=720),
  seed determinism, file output

Unity scaffold (ondevice/Scripts/):
- CommandBank.cs: loads commands.json from StreamingAssets (WebRequest on Android)
- TtsManager.cs: sherpa-onnx integration with [SHERPA] stubs, StreamingAssets→
  persistentDataPath copy, defensive Speak() with onDone callback
- SessionController.cs: passthrough flow §6, level switching, busy guard on Next
- ondevice/README.md: full manual Editor steps §5 (Unity setup, Meta XR SDK,
  sherpa-onnx install, scene wiring, font, build, metrics)

pytest: 37/37 passed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 16:11:53 +02:00

108 lines
3.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Bank poleceń do użycia na urządzeniu (ONDEVICE §4.1)."""
import argparse
import json
import random
import sys
from itertools import product
from typing import Optional
from .generator import Clause, Command, random_command, render
from .lexicon import COLORS, LOCATIONS, OBJECTS, VERBS
# Rozmiary przestrzeni:
# Poziom 1: 2 × 10 × 6 = 120 unikatów
# Poziom 2: 2 × 10 × 6 × 6 = 720 unikatów
# Poziom 3: ~635 040 unikatów → generujemy losowo z deduplikacją
def _enumerate_level1() -> list[str]:
texts = []
for v, o, loc in product(VERBS, OBJECTS, LOCATIONS):
texts.append(render(Command([Clause(v, o, loc, None)], 1)))
return texts
def _enumerate_level2() -> list[str]:
texts = []
for v, o, loc, c in product(VERBS, OBJECTS, LOCATIONS, COLORS):
texts.append(render(Command([Clause(v, o, loc, c)], 2)))
return texts
def _random_unique_level3(target: int) -> list[str]:
seen: set[str] = set()
results: list[str] = []
consecutive_misses = 0
while len(results) < target and consecutive_misses < 500:
text = render(random_command(3))
if text not in seen:
seen.add(text)
results.append(text)
consecutive_misses = 0
else:
consecutive_misses += 1
return results
def generate_bank(per_level: int, seed: Optional[int] = None) -> dict:
"""Zwraca słownik gotowy do zrzutu jako JSON.
Dla poziomów 1 i 2 wylicza wyczerpująco wszystkie kombinacje i tasuje;
dla poziomu 3 generuje losowo z deduplikacją.
Jeśli przestrzeń jest mniejsza niż per_level, zwraca tyle ile możliwe.
"""
if seed is not None:
random.seed(seed)
commands: list[dict] = []
for level, pool_fn in [(1, _enumerate_level1), (2, _enumerate_level2)]:
pool = pool_fn()
random.shuffle(pool)
for text in pool[:per_level]:
commands.append({"level": level, "text": text})
for text in _random_unique_level3(per_level):
commands.append({"level": 3, "text": text})
return {
"meta": {"version": "1", "per_level": per_level},
"commands": commands,
}
def run_export(argv: list[str]) -> None:
parser = argparse.ArgumentParser(
prog="python -m ipin_vr export",
description="Generuj bank poleceń do JSON (ONDEVICE §4.1)",
)
parser.add_argument(
"--per-level", type=int, default=300, metavar="N",
help="liczba unikatów na poziom (domyślnie: 300)",
)
parser.add_argument(
"--out", default="-", metavar="PATH",
help="ścieżka pliku wyjściowego (- = stdout)",
)
parser.add_argument(
"--seed", type=int, metavar="N",
help="seed generatora losowego",
)
args = parser.parse_args(argv)
bank = generate_bank(per_level=args.per_level, seed=args.seed)
output = json.dumps(bank, ensure_ascii=False, indent=2)
if args.out == "-":
print(output)
else:
with open(args.out, "w", encoding="utf-8") as f:
f.write(output)
by_level = {}
for cmd in bank["commands"]:
by_level[cmd["level"]] = by_level.get(cmd["level"], 0) + 1
summary = ", ".join(f"L{k}:{v}" for k, v in sorted(by_level.items()))
total = len(bank["commands"])
print(f"Zapisano {total} poleceń → {args.out} ({summary})", file=sys.stderr)