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>
108 lines
3.3 KiB
Python
108 lines
3.3 KiB
Python
"""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)
|