KAZIK/tools/fortune_ai/package.py
Artvasil09 ce06659ab2 feat: implement offline neural fortune teller and native tarot divination
- Offline neural worker based on llama.cpp (fortune-engine.exe) with local Qwen3-4B model support

- Native assembly Tarot 3-card spread divination and teacher grade divination mode

- Text input dialog in MODE_FORTUNE communicating over anonymous pipes

- Clean process lifecycle management, WM_CHAR dispatch and fallback handling

- Test suites and packaging tools for offline release
2026-10-21 13:53:06 +03:00

52 lines
2.5 KiB
Python

"""Create a self-contained ZIP64 release, including the offline model."""
from pathlib import Path
import argparse
import hashlib
import json
import zipfile
ROOT = Path(__file__).resolve().parents[2]
def sha(path):
with path.open('rb') as source:
return hashlib.file_digest(source, 'sha256').hexdigest()
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--check-only', action='store_true')
args = parser.parse_args()
manifest = json.loads((ROOT / 'ai/model.json').read_text('utf8'))
model = ROOT / 'ai' / manifest['file']
assert model.stat().st_size == manifest['bytes']
assert sha(model) == manifest['sha256']
game = ROOT / 'bin/poker.exe'
assert b'ai\\fortune-engine.exe' in game.read_bytes(), 'game must include the native AI integration'
assert sha(game) == sha(ROOT / 'poker.exe') == sha(ROOT / 'build/graphics/poker.exe')
assert sha(game).upper() in (ROOT / 'build/AssemblerCasinoLauncher.cs').read_text('utf8')
files = [(ROOT / 'dist/Assembler Casino.exe', 'Assembler Casino.exe'),
(game, 'bin/poker.exe')]
files += [(p, p.relative_to(ROOT).as_posix()) for p in sorted((ROOT / 'assets').rglob('*')) if p.is_file()]
files += [(p, p.relative_to(ROOT).as_posix()) for p in sorted((ROOT / 'ai').rglob('*'))
if p.is_file() and p.suffix != '.part']
assert (ROOT / 'ai/fortune-engine.exe', 'ai/fortune-engine.exe') in files
assert (ROOT / 'ai/licenses/Qwen.txt', 'ai/licenses/Qwen.txt') in files
assert (ROOT / 'ai/licenses/llama.cpp.txt', 'ai/licenses/llama.cpp.txt') in files
if args.check_only:
print(f'PASS: complete offline bundle, pinned model hash, matching game and launcher; {len(files)} files')
return
output = ROOT / 'dist/Assembler Casino Offline.zip'
with zipfile.ZipFile(output, 'w', compression=zipfile.ZIP_DEFLATED, compresslevel=1, allowZip64=True) as archive:
for path, name in files:
archive.write(path, name, compress_type=zipfile.ZIP_STORED if path == model else zipfile.ZIP_DEFLATED)
with zipfile.ZipFile(output) as archive:
entry = archive.getinfo('ai/' + manifest['file'])
assert entry.file_size == manifest['bytes']
assert len(archive.infolist()) == len(files)
assert archive.testzip() is None, 'release archive failed its CRC check'
print(f'Created self-contained ZIP64 bundle: {output} ({output.stat().st_size / 1e9:.2f} GB)')
if __name__ == '__main__':
main()