mirror of
https://github.com/migatu/vtt_work.git
synced 2026-07-14 21:38:39 +00:00
183 lines
9.2 KiB
Python
183 lines
9.2 KiB
Python
# generate_manifests_v2.py
|
||
# Fortel Trappa — manifesty o wysokiej różnorodności (HOLD I/II/III) w dwóch ZIP-ach.
|
||
# Uruchom: python generate_manifests_v2.py
|
||
# Wynik: FORTEL_MANIFESTS_v2_FULL.zip oraz FORTEL_MANIFESTS_v2_HOLD_I.zip
|
||
|
||
import random, zipfile, datetime
|
||
|
||
# ===== USTAWIENIA =====
|
||
SEED = None # ustaw np. na liczbę, jeśli chcesz powtarzalny wynik
|
||
SUSP_RATIO = 0.34 # ~1/3 pozycji ma „prawdę” w ( ) o xeno/kontrabandzie
|
||
QTY_MIN, QTY_MAX = 1, 10
|
||
LINES_PER_SECTION = 123
|
||
CAP_PER_SECTION = 1000
|
||
|
||
if SEED is None:
|
||
random.seed() # świeża losowość
|
||
else:
|
||
random.seed(SEED)
|
||
|
||
# ===== PULI I KOMPONENTY NAZW =====
|
||
WORLDS = [
|
||
"Ostia","Avachrus","Enoch","Gilead Prime","Nethreus","Ryza","Vostroya","Scintilla","Footfall",
|
||
"Hydraphur","Malfi","Port Wander","Voss Prime","Lucius","Graia","Metalica","Sollex","Sibellus"
|
||
]
|
||
|
||
FOODS = [
|
||
"Ziarno kultywacyjne","Pszenica","Owies","Jęczmień","Mąka","Kasza jęczmienna","Kasza owsiana",
|
||
"Groch","Fasola","Ciecierzyca","Słód jasny","Słód ciemny","Suszone jarzyny","Sól żywnościowa","Olej lampowy"
|
||
]
|
||
INDUSTRY = [
|
||
"Złącza heksowe","Zawory nadciśnieniowe","Łożyska baryłkowe","Łożyska kulkowe","Łożyska stożkowe",
|
||
"Taśmy stalowe","Plandeki światłochłonne","Pudła ciszy (wygłuszenie)","Rury segmentowe PL","Kable PL",
|
||
"Siatki zabezpieczające","Zasuwy grodziowe","Zaczepy stalowe","Kątowniki ciężkie","Nity kadłubowe",
|
||
"Skrzynki na dźwięk (wygłuszenie)","Filtry rebreatherów","Transpondery nawigacyjne","Przekaźniki proste",
|
||
"Zawiesia wielokrążkowe","Sprężyny powrotne","Kołnierze izolacyjne","Kleje statkowe","Klemy zasilające",
|
||
"Rolki transportowe","Farba kadłubowa","Żywica naprawcza","Kity hermetyczne","Szkła ochronne",
|
||
"Panele grodziowe","Uszczelki retencyjne","Siłowniki serwo (obudowy)"
|
||
]
|
||
DEVOTION = [
|
||
"Lampiony procesyjne","Witraże polowe (składane)","Zwoje pergaminowe","Maski nabożne",
|
||
"Relikwie podręczne (skrzynki)","Szkatuły dewocyjne (stal miękka)","Amfory gliniane",
|
||
"Szaty ‘Pokora’ (robocze)","Kopułki modlitewne (mini)","Płaszcze ‘Wachta’",
|
||
"Świece czarne ‘Negocjacje’","Świece woskowe ‘Cisza Nocna’","Figurki pobożne",
|
||
"Piety miniaturowe","Popiół posypny (liturgiczny)","Ołtarze polowe (składane)",
|
||
"Skarbonki parafialne","Księgi wizualne (albumy)","Księgi zakładkowe",
|
||
"Ikony ścienne (odlewy)","Reliefy miedziane","Baldachimy ‘Krem’","Puchary kapłańskie (rekwizyt)"
|
||
]
|
||
|
||
ADJ = [
|
||
"‘Hostia’","‘Pielgrzym’","‘Kocioł’","‘Pilgrim’","‘Stałość’","‘Ordo Cargo’","‘Mgła’","‘Cień’",
|
||
"‘Błękit Służbowy’","‘Sztywna Karta’","‘Jedność’","‘Głuchy Spaw’","‘Kierunek’","‘Gotowość’","‘Neutral’",
|
||
"‘Mk II’","‘Mk III’","‘Mk IV’","‘Mk V’","‘Plus’","‘Premium’","‘Export’","‘Seria V’","‘Seria IX’","‘Nocny’"
|
||
]
|
||
|
||
INN_NOTES = [
|
||
"Zwykły wyrób, zgodny z normą diecezjalną.",
|
||
"Do użytku cywilnego; dokumentacja pełna.",
|
||
"Na potrzeby konserwacji/edukacji polowej.",
|
||
"Brak zasilania, brak funkcji specjalnych.",
|
||
"Wersja eksportowa; gwinty zgodne z PL-3.",
|
||
"Na morale i porządek; bez cech bojowych.",
|
||
"Próbki kontrolne — wynik negatywny na scrapcode.",
|
||
"Rekwizyty katechetyczne; brak statusu kultowego.",
|
||
"Puste, czyste, opieczętowane.",
|
||
"Zabezpieczone audytem Ministorum.",
|
||
"Składane; bez ostrych krawędzi w papierach.",
|
||
"Zwykła jakość magazynowa; brak niespodzianek.",
|
||
"Brak reakcji psionicznej (pomiar: tło).",
|
||
"Standard przemysłowy; kompatybilność wysoka.",
|
||
"Do ekspozycji; nieotwierane przy inspekcji.",
|
||
"Zestaw muzealny; kojąco nudny.",
|
||
"Oznaczenie ‘cywilne’; instrukcja w komplecie.",
|
||
"Zgodne z przepisem P-77 (ładownie ogólne).",
|
||
"Pieczęć diecezjalna aktualna; datowanie bieżące.",
|
||
"Bezpieczne dla chóru i czytników vox."
|
||
]
|
||
|
||
HARMLESS = [
|
||
"zwykły ładunek cywilny","materiały szkoleniowe/rekonstrukcja","zestaw konserwatorski",
|
||
"komplet części serwisowych","puste i czyste","standard magazynowy","produkt spożywczy",
|
||
"wyposażenie pomocnicze","materiały dekoracyjne","narzędzia warsztatowe"
|
||
]
|
||
|
||
XENO_REAL = [
|
||
"fragmenty wraithbone Aeldari (martwe, obrobione ‘na szkło’)",
|
||
"odpryski spirit-stone (pozbawione ładunku)",
|
||
"ukryty vox-scrambler xeno (obudowa cywilna)",
|
||
"mikrofiolki po jadu Lhamaean (dokładnie wyparzone)",
|
||
"moduł holo-emisyjny Aeldari (matryca wycięta, nieaktywny)",
|
||
"kaseta rozliczeń cold trade (podwójne dno)",
|
||
"nekrotech — fragment stele ‘bezcielesnej’ (kamień ‘ozdobny’)",
|
||
"obudowy tłumiące sygnał (technologia nieidentyfikowana)",
|
||
"orb psioniczny — tłumik nastroju (źródło Aeldari)",
|
||
"halo-device: element oprawy (niekompletne, bez rdzenia)",
|
||
"resztki proscribed tomes (okładki, wkład wymieniony na blanki)",
|
||
"zegar-pikietaż dla transakcji cold trade (ukryty znacznik czasu)",
|
||
"ampułki po obscura klasy ceremonialnej (umyte; testy czyste)",
|
||
"fiolki po ‘ichorze’ drukhari (sterylnie czyste)",
|
||
"masker polowy Drukhari — tkanina z włóknem ziggurat (zdezaktywowana)",
|
||
"matryca soniczna Laer (obudowana ‘pudłem ciszy’)",
|
||
"półprzezroczysta łuska necron — fragment dekoracyjny (brak reakcji)",
|
||
]
|
||
|
||
def compose_name():
|
||
# wybór kategorii z wagami
|
||
genre = random.choices(["food","industry","devotion"], [4,5,3])[0]
|
||
base = random.choice({"food":FOODS, "industry":INDUSTRY, "devotion":DEVOTION}[genre])
|
||
# modyfikacje wersji/serii
|
||
if "PL" in base:
|
||
base = base.replace("PL", f"PL-{random.randint(3,12)}")
|
||
if "Kable PL" in base and random.random()<0.7:
|
||
base = base.replace("Kable PL", f"Kable PL-{random.randint(2,8)}")
|
||
flair=[]
|
||
if random.random()<0.7: flair.append(random.choice(ADJ))
|
||
if random.random()<0.25: flair.append(f"‘Seria {random.randint(2,12)}’")
|
||
return base if not flair else f"{base} " + " ".join(flair)
|
||
|
||
def make_line(idx, suspicious_ratio=SUSP_RATIO):
|
||
q = random.randint(QTY_MIN, QTY_MAX)
|
||
nm = compose_name()
|
||
ow = random.choice(WORLDS)
|
||
note = random.choice(INN_NOTES)
|
||
paren = f"({random.choice(XENO_REAL)})" if random.random()<suspicious_ratio else f"({random.choice(HARMLESS)})"
|
||
return f"{idx}) {q}× — {nm} — {ow} — [{note}] {paren}", q
|
||
|
||
def make_section_text(hold_label, s_idx, s_cnt):
|
||
header = (
|
||
"=== FORTEL TRAPPA — MANIFEST ŁADUNKOWY ===\n"
|
||
f"{hold_label} / SEKCJA {s_idx} z {s_cnt}\n"
|
||
"(Format: [Liczba 1–10] × Standardowy Imperialny Kontener — Nazwa — Pochodzenie — [Uwaga kapitańska] (co to naprawdę jest))\n\n"
|
||
)
|
||
total=0; entries=[]
|
||
# ogranicz powtórzenia nazw w obrębie sekcji
|
||
seen=set()
|
||
for i in range(1, LINES_PER_SECTION+1):
|
||
tries=0
|
||
while True:
|
||
line,q = make_line(i)
|
||
name_part = line.split("—")[1].strip() # „N× — NAZWA — ...”
|
||
if name_part not in seen or tries>7:
|
||
seen.add(name_part)
|
||
break
|
||
tries+=1
|
||
entries.append([line,q]); total+=q
|
||
# przytnij sumę kontenerów do CAP_PER_SECTION
|
||
if total>CAP_PER_SECTION:
|
||
excess=total-CAP_PER_SECTION
|
||
for i in range(len(entries)-1,-1,-1):
|
||
if excess<=0: break
|
||
q=entries[i][1]
|
||
if q>1:
|
||
red=min(q-1,excess); entries[i][1]-=red; excess-=red
|
||
prefix,rest=entries[i][0].split("× —",1)
|
||
idx_str=prefix.split(")")[0]+")"
|
||
entries[i][0]=f"{idx_str} {entries[i][1]}× —{rest}"
|
||
body="\n".join(e[0] for e in entries)
|
||
footer=f"\n\n— KONIEC: {hold_label} / SEKCJA {s_idx}/{s_cnt} —"
|
||
return header+body+footer
|
||
|
||
def build_zip(full_path, hold1_path):
|
||
# FULL: HOLD I 04–16; HOLD II 01–15; HOLD III 01–15
|
||
with zipfile.ZipFile(full_path,"w",compression=zipfile.ZIP_DEFLATED) as z:
|
||
for s in range(4,17): # HOLD I
|
||
z.writestr(f"HOLD_I_SEKCJA_{s:02d}.txt", make_section_text("HOLD I", s, 16))
|
||
for s in range(1,16): # HOLD II
|
||
z.writestr(f"HOLD_II_SEKCJA_{s:02d}.txt", make_section_text("HOLD II", s, 15))
|
||
for s in range(1,16): # HOLD III
|
||
z.writestr(f"HOLD_III_SEKCJA_{s:02d}.txt", make_section_text("HOLD III", s, 15))
|
||
z.writestr("README.txt",
|
||
f"FORTEL TRAPPA — MANIFESTY v2 ({datetime.datetime.utcnow().isoformat()}Z)\n"
|
||
"HOLD I: 04–16; HOLD II: 01–15; HOLD III: 01–15\n"
|
||
f"~{int(SUSP_RATIO*100)}% pozycji w ( ) ujawnia real-tożsamość (xeno/kontrabanda); [ ] — noty urzędowo niewinne.\n"
|
||
"123 pozycji/sekcję; 1–10 kontenerów/pozycję; suma ≤1000/sekcję.\n")
|
||
# HOLD I only
|
||
with zipfile.ZipFile(hold1_path,"w",compression=zipfile.ZIP_DEFLATED) as z:
|
||
for s in range(4,17):
|
||
z.writestr(f"HOLD_I_SEKCJA_{s:02d}.txt", make_section_text("HOLD I", s, 16))
|
||
z.writestr("README_HOLD_I.txt", f"HOLD I (04–16) v2 — {datetime.datetime.utcnow().isoformat()}Z\n")
|
||
|
||
if __name__ == "__main__":
|
||
build_zip("FORTEL_MANIFESTS_v2_FULL.zip","FORTEL_MANIFESTS_v2_HOLD_I.zip")
|
||
print("OK -> FORTEL_MANIFESTS_v2_FULL.zip | FORTEL_MANIFESTS_v2_HOLD_I.zip")
|