# 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()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")