Game Update missing files

This commit is contained in:
DazedAnon 2026-07-20 11:56:04 -05:00
parent 2b825a02f3
commit 3311d81645
5 changed files with 87 additions and 74 deletions

View file

@ -1,65 +0,0 @@
# Game Characters
エル (El) - Female; inn adventurer tied to the Light Plain plotline; not player-chosen; speaks casually and informally; energetic and direct
流浪の求道者 (Seeker) - Male; roaming challenge NPC; not player-chosen; speaks in a grave, formal register; stoic and trial-focused
シスターエキュリー (Sister Ecurie) - Female; church sister of the Amol faith; not player-chosen; polite religious register; gentle and dutiful
ホエミ (Hoemi) - Female; friend in the northern district; not player-chosen; casual friendly speech; warm and approachable
ロクサヌ (Roxanu) - Female; northern district student; not player-chosen; youthful casual speech; sociable and ordinary
ミント (Mint) - Female; northern district student; not player-chosen; youthful casual speech; lively and informal
ヒルデ (Hilde) - Female; apartment resident; not player-chosen; plain civilian speech; practical and grounded
管理人ナミサ (Manager Namisa) - Female; apartment manager; not player-chosen; polite caretaker speech; responsible and matter-of-fact
デフィ (Defi) - Female; soldier's wife living in the apartment; not player-chosen; domestic casual speech; reserved and ordinary
ラム (Ram) - Female; arena warrior; not player-chosen; confident fighter's speech; bold and competitive
アルシェ (Arche) - Female; arena fan; not player-chosen; enthusiastic casual speech; excitable and friendly
バーニア (Barnia) - Female; student associated with the arena; not player-chosen; youthful casual speech; curious and energetic
フォサ (Fossa) - Female; arena-side NPC; not player-chosen; casual town speech; socially active
キャナリ (Canari) - Female; cafe worker or cafe patron; not player-chosen; light casual speech; cheerful and sociable
エロン (Elon) - Male; cafe NPC; not player-chosen; casual adult speech; relaxed and conversational
チコニャ (Ciconya) - Female; cafe NPC; not player-chosen; casual service speech; friendly and talkative
ルートル (Rootle) - Male; cafe NPC; not player-chosen; casual adult speech; easygoing
シャモー (Chameau) - Male; cafe NPC; not player-chosen; casual adult speech; relaxed and ordinary
コション (Cochon) - Male; cafe NPC; not player-chosen; casual adult speech; informal and sociable
アレニエ (Areignee) - Female; trading-company NPC; not player-chosen; businesslike polite speech; composed and practical
ミルバト (Mirbat) - Male; trading-company NPC; not player-chosen; businesslike speech; practical and restrained
受付アンヌ (Receptionist Anne) - Female; theater receptionist; not player-chosen; polite service register; courteous and professional
支配人アドルフ (Manager Adolf) - Male; theater manager; not player-chosen; formal managerial speech; authoritative and composed
アンリ (Henri) - Male; theater-related named NPC; not player-chosen; polite stage-world speech; refined and reserved
カトリーヌ (Catherine) - Female; theater performer or staff member; not player-chosen; elegant feminine speech; theatrical and poised
シエル (Ciel) - Female; theater performer or staff member; not player-chosen; polite feminine speech; graceful and composed
グラティア (Gratia) - Female; theater NPC; not player-chosen; refined speech; elegant and expressive
オレイユ (Oreille) - Female; theater NPC; not player-chosen; refined speech; attentive and polished
実況 (Commentator) - Male; arena or event commentator; not player-chosen; energetic announcing register; loud and performative
ウミ (Umi) - Female; named NPC in Albol Forest; not player-chosen; casual rural speech; practical and outdoorsy
ネリネ (Nerine) - Female; named NPC at Albol Camp; not player-chosen; casual camp speech; warm and grounded
ジャスミン (Jasmine) - Female; named NPC at Albol Camp; not player-chosen; casual feminine speech; friendly and composed
アーディ (Ardi) - Male; named NPC in the Testa settlement; not player-chosen; casual local speech; practical and direct
クラレット (Claret) - Female; named NPC in Viola Village; not player-chosen; casual village speech; grounded and approachable
ステノ (Steno) - Female; hot spring inn NPC; not player-chosen; polite hospitality speech; calm and service-minded
# Worldbuilding Terms
淫催都市ヒュプノズム (Lewd Hypnosis City Hypnosm) - Game title and central city setting; a city shaped by the Incitation curse and its social control
ヒュプノズム (Hypnosm) - Main city and hub area; divided into districts such as the southern district, slums, upper district, church, guild, theater, and lord's manor
淫催 (Incitation) - The central hypnosis-like curse or state that alters obedience, desire, and social values throughout Hypnosm
淫催の呪い (Incitation Curse) - Story term for the magical influence affecting the city and its residents
淫催の守護者 (Guardian of Incitation) - Enemy or boss title connected to the Incitation system
アモル教 (Amol Faith) - City religion imposed on residents; tied to marriage, sexuality, and the social norms of Hypnosm
光の平原 (Light Plain) - Major plot location where magical power gathers and expands; associated with danger, monsters, and the Divine King's awakening
神王覚醒 (Divine King's Awakening) - Historical event said to have created or transformed the Light Plain around twenty years earlier
血胤 (Bloodline) - Ruby-tagged lore term read as Bloodseed; unique hereditary magic tied to absorbing traits from other beings
血胤魔術 (Bloodline Magic) - Keimi's unique magic system derived from the Bloodline lineage
ブラッドシード家 (Bloodseed Family) - Fallen noble family associated with Bloodline magic
フィガロ家 (Figaro Family) - Prestigious family famous for swords and magic swords
魔剣 (Magic Sword) - Lore term for special enchanted swords, especially those connected to the Figaro family
フィガロの魔剣 (Figaro Magic Sword) - Named magic sword from the Figaro family
魔剣活性化 (Magic Sword Activation) - Story process or quest concept for making a magic sword usable
冒険者ランク (Adventurer Rank) - Guild status system; raising it unlocks citizenship and marriage-related progression
住民権 (Citizenship) - Legal status required to access restricted districts and marry within Hypnosm
ギルド (Guild) - Adventurer organization that handles rank progression and quest reporting
マナ (Mana) - Recurring magical resource gained from exploration and used in city systems
マナ屋 (Mana Shop) - City facility or service related to exchanging mana
領主の館 (Lord's Manor) - Plot location tied to the city's ruler and later story investigation
地下道 (Underground Passage) - Story route used during the Hypnosm investigation arc
魔人の塔 (Demon Tower) - Major dungeon or travel destination referenced in progression data
氷結の迷宮 (Frozen Labyrinth) - Major dungeon or travel destination referenced in progression data
宵闇のエージェント (Agent of Twilight) - Named quest title with story significance
極寒のプレリュード (Frigid Prelude) - Named quest title with story significance
性奴隷 (Sex Slave) - In-universe class/status used by event progression and relationship systems

View file

@ -74,8 +74,18 @@ class UpdateThread(QThread):
ARCHIVE_ROOT = "dazed-mtl-tool"
SHA_FILE = str(LAST_UPDATE_SHA_PATH)
# Paths (relative, top-level) that should never be touched during update
PROTECTED = {".env", "venv", "log", "files", "translated", "data"}
# Top-level paths that should never be touched during update
PROTECTED_TOP = {".env", "venv", "log", "files", "translated"}
# User-local files under data/ that must not be overwritten.
# Shipped defaults (translation_contexts.json, skills/*.md, vocab_base.txt, …)
# are intentionally updated so tool releases refresh prompts and contexts.
PROTECTED_DATA_FILES = frozenset({
"data/vocab.txt",
"data/last_update_sha.txt",
"data/wolf_speakers.json",
"data/wolf_safe_notes.json",
})
# GitLab zip archives do not preserve Unix execute bits; restore after apply.
EXECUTABLE_SUFFIXES = {".sh", ".desktop"}
@ -88,6 +98,18 @@ class UpdateThread(QThread):
def __init__(self, parent=None):
super().__init__(parent)
@classmethod
def should_install(cls, rel: Path) -> bool:
"""Return True when *rel* (path relative to archive root) may be copied."""
parts = rel.parts
if not parts:
return False
if parts[0] in cls.PROTECTED_TOP:
return False
if rel.as_posix() in cls.PROTECTED_DATA_FILES:
return False
return True
@staticmethod
def _fmt_bytes(num: int) -> str:
if num >= 1024 * 1024:
@ -188,9 +210,7 @@ class UpdateThread(QThread):
install_files = [
src
for src in extracted.rglob("*")
if src.is_file()
and src.relative_to(extracted).parts
and src.relative_to(extracted).parts[0] not in self.PROTECTED
if src.is_file() and self.should_install(src.relative_to(extracted))
]
total_files = max(len(install_files), 1)

View file

@ -85,6 +85,7 @@ from gui.translation_tab import (
BATCH_MODE_LABEL,
)
from gui.workflow_tab import (
_GAMEUPDATE_COPY_SKIP_NAMES,
_FileCopyWorker,
_ImportWorker,
_make_btn,
@ -1396,7 +1397,7 @@ class WolfWorkflowTab(QWidget):
if not Path(src).is_dir():
self._log(f"⚠ gameupdate folder not found: {src}")
return
w = _FileCopyWorker(src, dst)
w = _FileCopyWorker(src, dst, skip_names=_GAMEUPDATE_COPY_SKIP_NAMES)
w.log.connect(self._log)
w.done.connect(self._on_gameupdate_done)
self._import_worker = w # reuse slot; not WolfTaskWorker
@ -1445,6 +1446,9 @@ class WolfWorkflowTab(QWidget):
for fp in sorted(Path(src).rglob("*")):
if not fp.is_file():
continue
if fp.name in _GAMEUPDATE_COPY_SKIP_NAMES:
log(f" skipped {fp.relative_to(src)}")
continue
rel = fp.relative_to(src)
target = Path(dst) / rel
try:

View file

@ -312,15 +312,23 @@ class _JsonFormatWorker(QThread):
self.done.emit(False, f"dazedformat error: {exc}")
# Never copy these into a game root when applying gameupdate/ (local updater state
# or stray translator assets that must not overwrite a live install).
_GAMEUPDATE_COPY_SKIP_NAMES = frozenset({
"previous_patch_sha.txt",
})
class _FileCopyWorker(QThread):
"""Recursively copy a source folder into a destination folder."""
done = pyqtSignal(int, list) # count_copied, errors
log = pyqtSignal(str)
def __init__(self, src: str, dst: str):
def __init__(self, src: str, dst: str, skip_names: frozenset[str] | None = None):
super().__init__()
self.src = src
self.dst = dst
self.skip_names = skip_names or frozenset()
def run(self):
import shutil
@ -336,6 +344,9 @@ class _FileCopyWorker(QThread):
for fp in src.rglob("*"):
if not fp.is_file():
continue
if fp.name in self.skip_names:
self.log.emit(f" skipped {fp.relative_to(src)}")
continue
rel = fp.relative_to(src)
target = dst / rel
try:
@ -4281,7 +4292,7 @@ class WorkflowTab(QWidget):
if not Path(src).is_dir():
self._log(f"⚠ gameupdate folder not found: {src}")
return
w = _FileCopyWorker(src, dst)
w = _FileCopyWorker(src, dst, skip_names=_GAMEUPDATE_COPY_SKIP_NAMES)
w.log.connect(self._log)
w.done.connect(self._on_gameupdate_done)
self._worker = w
@ -4316,7 +4327,14 @@ class WorkflowTab(QWidget):
self._log(f" ⏭ Skipped: B (format plugins.js): not found ({plugins_js or 'not set'})")
if gameupdate_src and Path(gameupdate_src).is_dir() and game_root_dst:
queue.append(("[C] gameupdate copy", _FileCopyWorker(gameupdate_src, game_root_dst)))
queue.append((
"[C] gameupdate copy",
_FileCopyWorker(
gameupdate_src,
game_root_dst,
skip_names=_GAMEUPDATE_COPY_SKIP_NAMES,
),
))
else:
reason = (f"source not found ({gameupdate_src or 'not set'})"
if not gameupdate_src or not Path(gameupdate_src).is_dir()

View file

@ -15,6 +15,42 @@ from unittest.mock import patch
from util.wolfdawn import WolfDawnError, bundled_binary_path, ensure_wolf_binary
class UpdateThreadInstallFilterTests(unittest.TestCase):
"""Tool update must refresh shipped data/ assets while protecting user state."""
def test_installs_translation_contexts_and_skills(self):
from gui.main import UpdateThread
for rel in (
"data/translation_contexts.json",
"data/skills/system.md",
"data/skills/project_setup.md",
"data/vocab_base.txt",
"gui/main.py",
"gameupdate/GameUpdate.bat",
"gameupdate/gameupdate/patch.ps1",
):
with self.subTest(rel=rel):
self.assertTrue(UpdateThread.should_install(Path(rel)))
def test_skips_user_local_data_and_workdir(self):
from gui.main import UpdateThread
for rel in (
"data/vocab.txt",
"data/last_update_sha.txt",
"data/wolf_speakers.json",
"data/wolf_safe_notes.json",
".env",
"venv/pyvenv.cfg",
"log/translations.txt",
"files/Map001.json",
"translated/Map001.json",
):
with self.subTest(rel=rel):
self.assertFalse(UpdateThread.should_install(Path(rel)))
class CheckToolUpdateTests(unittest.TestCase):
"""Tool update check compares remote SHA to data/last_update_sha.txt only."""