Shakshuka Man's python scripts
How to setup[edit]
The following is a single-file python module. To use, save the below as iw.py.
iw.py source code
from __future__ import annotations
import hashlib
import json
import random
import string
import uuid
from dataclasses import asdict, dataclass, field, fields, is_dataclass, MISSING
from enum import StrEnum
from typing import Any, Optional, Union, get_type_hints, get_origin, get_args
# ---- ID Generation ---------------------------------------------------------
_SHORT_ID_CHARS = string.ascii_letters + string.digits
_SHORT_ID_FIRST_CHARS = string.ascii_letters
class IDGenerator:
"""Deterministic ID generator. A single shared instance ensures uniqueness
across all call sites without requiring manual ID management."""
def __init__(self, seed: int = 0):
self._rng = random.Random(seed)
def next_uuid(self) -> str:
return str(uuid.UUID(int=self._rng.getrandbits(128), version=4))
def next_short(self, length: int = 8) -> str:
first = self._rng.choice(_SHORT_ID_FIRST_CHARS)
rest = ''.join(self._rng.choice(_SHORT_ID_CHARS) for _ in range(length - 1))
return first + rest
def seed(self, value: int) -> None:
self._rng = random.Random(value)
_id = IDGenerator()
# ---- Enums -----------------------------------------------------------------
class TrackedItemDataType(StrEnum):
TEXT = "text"
NUMBER = "number"
XML = "xml"
class TrackedItemVisibility(StrEnum):
EVERYONE = "everyone"
AI_ONLY = "ai_only"
PLAYER_ONLY = "player_only"
HIDDEN = "hidden"
class TrackedItemInitialValueSource(StrEnum):
SAME = "same"
CHARACTER = "character"
PLAYER = "player"
class TrackedItemAction(StrEnum):
SET = "set"
ADD = "add"
SUBTRACT = "subtract"
REPLACE = "replace"
class Inequality(StrEnum):
IS_EXACTLY = "is_exactly"
AT_LEAST = "at_least"
AT_MOST = "at_most"
class EffectType(StrEnum):
SHOW_MESSAGE = "effectShowMessage"
TELL_AI = "effectTellAIWhatToDo"
CHANGE_MAIN_INSTRUCTIONS = "effectChangeMainInstructions"
MODIFY_INSTRUCTION_BLOCK = "effectModifyInstructionBlock"
CHANGE_AUTHOR_STYLE = "effectChangeAuthorStyle"
CHANGE_DESCRIPTION_INSTRUCTIONS = "effectChangeDescriptionInstructions"
CHANGE_OBJECTIVE = "effectChangeObjective"
CHANGE_VICTORY_CONDITION = "effectChangeVictoryCondition"
CHANGE_DEFEAT_CONDITION = "effectChangeDefeatCondition"
CHANGE_PC_NAME = "effectChangePCName"
CHANGE_PC_DESCRIPTION = "effectChangePCDescription"
CHANGE_PC_SKILL = "effectChangePCSkill"
SET_TRACKED_ITEM_VALUE = "effectSetTrackedItemValue"
FIRE_RANDOM_TRIGGER = "effectFireRandomTrigger"
MODIFY_KEYWORD_BLOCK = "effectModifyKeywordBlock"
ENDS_GAME = "effectEndsGame"
MODIFY_TRACKED_ITEM_DETAILS = "effectModifyTrackedItemDetails"
class ConditionType(StrEnum):
ON_TURN = "triggerOnTurn"
ON_EVENT = "triggerOnEvent"
ON_CHARACTER = "triggerOnCharacter"
ON_TRACKED_ITEM = "triggerOnTrackedItem"
ON_RANDOM_CHANCE = "triggerOnRandomChance"
PREREQS = "triggerPrereqs"
class LogicOperator(StrEnum):
AND = "and"
OR = "or"
# ---- Dataclasses -----------------------------------------------------------
@dataclass
class ImagePromptDetails:
illustrClothes: str = ""
illustrSetting: str = ""
illustrSubject: str = ""
illustrAppearance: str = ""
illustrIsCharacter: bool = True
illustrExpressionPosition: str = ""
@dataclass
class PermissionsOnceShared:
sharing: bool = True
editing: bool = True
@dataclass
class PortraitPromptDetails:
illustrClothes: str = ""
illustrSetting: str = ""
illustrAppearance: str = ""
illustrExpressionPosition: str = ""
@dataclass
class VictoryDefeatCondition:
condition: str
text: str
alreadyFired: bool = False
@dataclass
class InitialTrackedItemValue:
id: str
name: str
initialPCValue: str = ""
visibility: TrackedItemVisibility = TrackedItemVisibility.AI_ONLY
initialValueBasedOnPC: TrackedItemInitialValueSource = TrackedItemInitialValueSource.CHARACTER
@dataclass
class TrackedItem:
name: str
id: str = field(default_factory=_id.next_short)
dataType: TrackedItemDataType = TrackedItemDataType.TEXT
visibility: TrackedItemVisibility = TrackedItemVisibility.EVERYONE
description: str = ""
updateInstructions: str = ""
initialValue: str = ""
initialValueBasedOnPC: TrackedItemInitialValueSource = TrackedItemInitialValueSource.SAME
autoUpdate: bool = False
@dataclass
class NPC:
name: str
id: str = field(default_factory=_id.next_short)
detail: str = ""
one_liner: str = ""
appearance: str = ""
location: str = ""
secret_info: str = ""
names: list[str] = field(default_factory=list)
img_appearance: str = ""
img_clothing: str = ""
@dataclass
class InstructionBlock:
name: str
content: str
id: str = field(default_factory=_id.next_short)
selectedAIProfiles: Optional[list[str]] = None
@dataclass
class LoreBookEntry:
name: str
content: str
id: str = field(default_factory=_id.next_short)
keywords: list[str] = field(default_factory=list)
@dataclass
class PossibleCharacter:
name: str
description: str = ""
portrait: str = ""
portraitPromptDetails: PortraitPromptDetails = field(default_factory=PortraitPromptDetails)
fullSizePortrait: str = ""
portraitOptions: list[str] = field(default_factory=list)
fullSizePortraitOptions: list[str] = field(default_factory=list)
currentPortraitIndex: int = 0
characterId: str = field(default_factory=_id.next_short)
skills: dict[str, int] = field(default_factory=dict)
initialTrackedItemValues: list[InitialTrackedItemValue] = field(default_factory=list)
@dataclass
class TriggerEffect:
type: EffectType
data: Any
id: str = field(default_factory=_id.next_uuid)
trackedItemID: Optional[str] = None
@dataclass
class TriggerCondition:
data: Any
id: str = field(default_factory=_id.next_uuid)
type: Optional[ConditionType] = None
category: Optional[str] = None
trackedItemID: Optional[str] = None
inequality: Optional[Inequality] = None
operator: Optional[LogicOperator] = None
@dataclass
class TriggerEvent:
name: str
id: str = field(default_factory=_id.next_short)
triggerEffects: list[TriggerEffect] = field(default_factory=list)
triggerConditions: list[TriggerCondition] = field(default_factory=list)
advancedLogic: Optional[bool] = None
triggerOnStartOfGame: Optional[bool] = None
canTriggerMoreThanOnce: Optional[bool] = None
# ---- World -----------------------------------------------------------------
_OPTIONAL_FIELDS: set[str] = {
"descriptionRequest", "summaryRequest", "charSelectText",
"instructionBlocks", "loreBookEntries", "trackedItems", "NPCs",
"version", "autoAdvanceVersion", "designNotes",
}
def _convert(hint, val):
"""Convert a JSON value to the appropriate Python type based on a type hint."""
if val is None:
return None
origin = get_origin(hint)
args = get_args(hint)
if origin is Union:
non_none = [a for a in args if a is not type(None)]
if len(non_none) == 1:
return _convert(non_none[0], val)
return val
if origin is list and args:
return [_convert(args[0], item) for item in val]
if origin is dict:
return val
if is_dataclass(hint):
return _build(hint, val)
if isinstance(hint, type) and issubclass(hint, StrEnum):
return hint(val)
return val
_IGNORED_FIELDS = {"positionInList"}
def _build(cls, data: dict):
"""Construct a dataclass instance from a dict, recursively converting nested types."""
hints = get_type_hints(cls)
known = {f.name for f in fields(cls)} | _IGNORED_FIELDS
unknown = set(data) - known
if unknown:
raise NotImplementedError(
f"{cls.__name__} has no fields for: {', '.join(sorted(unknown))}"
)
kwargs = {}
for f in fields(cls):
if f.name not in data:
continue
kwargs[f.name] = _convert(hints[f.name], data[f.name])
return cls(**kwargs)
def _strip_none(obj):
"""Recursively remove None values from dicts (and nested structures)."""
if isinstance(obj, dict):
return {k: _strip_none(v) for k, v in obj.items() if v is not None}
if isinstance(obj, list):
return [_strip_none(x) for x in obj]
return obj
@dataclass
class World:
favorite: bool = False
title: str = ""
description: str = ""
background: str = ""
instructions: str = ""
authorStyle: str = ""
recommendedAIModel: Optional[str] = None
firstInput: str = ""
objective: str = ""
imageModel: str = "manticore"
imageStyle: str = "photo_1"
illustrationStyleNonCharacterLowPriority: str = ""
illustrationStyleNonCharacterHighPriority: str = ""
illustrationStyleCharacterLowPriority: str = ""
illustrationStyleCharacterHighPriority: str = ""
imageStyleCharacterPre: str = ""
imageStyleCharacterPost: str = ""
imageStyleNonCharacterPre: str = ""
imageStyleNonCharacterPost: str = ""
nsfw: bool = False
contentWarnings: str = ""
enableAISpecificInstructionBlocks: bool = False
previewImage: str = ""
fullSizePreviewImage: str = ""
previewImageOptions: list[str] = field(default_factory=list)
fullSizePreviewImageOptions: list[str] = field(default_factory=list)
currentPreviewImageIndex: int = -1
imagePromptDetails: ImagePromptDetails = field(default_factory=ImagePromptDetails)
permissionsOnceShared: PermissionsOnceShared = field(default_factory=PermissionsOnceShared)
allowChangeCharacterName: bool = True
allowChangeCharacterDescription: bool = True
allowChangeCharacterSkills: bool = False
allowChangeCharacterItemValues: bool = False
allowChangeCharacterPortrait: bool = True
allowChangeCharacterNewPortrait: bool = True
schemaVersion: Optional[int] = 2
skills: list[str] = field(default_factory=list)
possibleCharacters: list[PossibleCharacter] = field(default_factory=list)
triggerEvents: list[TriggerEvent] = field(default_factory=list)
victoryCondition: Optional[VictoryDefeatCondition] = None
defeatCondition: Optional[VictoryDefeatCondition] = None
# Optional fields — only included in JSON when non-default
descriptionRequest: str = ""
summaryRequest: str = ""
charSelectText: Optional[str] = None
instructionBlocks: list[InstructionBlock] = field(default_factory=list)
loreBookEntries: list[LoreBookEntry] = field(default_factory=list)
trackedItems: list[TrackedItem] = field(default_factory=list)
NPCs: list[NPC] = field(default_factory=list)
version: str = "0.01"
autoAdvanceVersion: bool = True
designNotes: Optional[str] = None
def to_dict(self) -> dict:
raw = asdict(self)
result = {}
for f in fields(self):
val = raw[f.name]
if f.name in _OPTIONAL_FIELDS:
if f.default is not MISSING and val == f.default:
continue
if f.default_factory is not MISSING and val == f.default_factory():
continue
if isinstance(val, list):
val = [_strip_none(item) for item in val]
result[f.name] = val
for key in ("trackedItems", "NPCs"):
for i, item in enumerate(result.get(key, [])):
item["positionInList"] = i
return result
def to_json(self) -> str:
return json.dumps(self.to_dict(), indent=4)
@classmethod
def from_dict(cls, data: dict) -> World:
# Re-seed the ID generator with the hash of the data to avoid ID collisions
_id.seed(int(hashlib.sha256(json.dumps(data, sort_keys=True).encode()).hexdigest(), 16))
return _build(cls, data)
@classmethod
def from_json(cls, json_str: str) -> World:
return cls.from_dict(json.loads(json_str))
How to use[edit]
Creating and loading worlds[edit]
import iw
# Create a minimal world
w = iw.World(title="My World")
print(w.to_json())
# Load an existing world
w = iw.World.from_json(open("my_world.json").read())
Updating an existing world[edit]
import iw
# Load
w = iw.World.from_json(open("my_world.json").read())
# Tweak
w.trackedItems.append(iw.TrackedItem(name="Sanity", initialValue="100"))
w.title = "Updated World"
# Export
with open("my_world.json", "w") as f:
f.write(w.to_json())
Inserting items mid-list[edit]
The display order of tracked items and NPCs is determined by their position in the Python list — positionInList is assigned automatically on export. Use standard list operations to reorder:
import iw
w = iw.World.from_json(open("my_world.json").read())
# Insert a new tracked item at position 1 (between the first and second items)
w.trackedItems.insert(1, iw.TrackedItem(name="Morale", initialValue="High"))
# The exported JSON will have positionInList 0, 1, 2, ... assigned automatically
with open("my_world.json", "w") as f:
f.write(w.to_json())
Programmatically generating world elements[edit]
Generate triggers from data. This example creates health-threshold triggers — at 75, 50, 25, and 0 HP:
import iw
health = iw.TrackedItem(
name="Health",
dataType=iw.TrackedItemDataType.NUMBER,
visibility=iw.TrackedItemVisibility.EVERYONE,
initialValue="100",
)
thresholds = [
(75, "You're bruised and battered."),
(50, "You're badly wounded. Find help soon."),
(25, "You can barely stand. Death is near."),
(0, "You collapse. The world fades to black."),
]
triggers = []
for hp, message in thresholds:
triggers.append(iw.TriggerEvent(
name=f"Health drops to {hp}",
triggerConditions=[
iw.TriggerCondition(
data={
"inequality": iw.Inequality.AT_MOST,
"requiredValue": str(hp),
"trackedItemID": health.id,
},
type=iw.ConditionType.ON_TRACKED_ITEM,
category="condition",
inequality=iw.Inequality.AT_MOST,
trackedItemID=health.id,
),
],
triggerEffects=[
iw.TriggerEffect(type=iw.EffectType.SHOW_MESSAGE, data=message),
],
))
# The final threshold also ends the game
triggers[-1].triggerEffects.append(
iw.TriggerEffect(type=iw.EffectType.ENDS_GAME, data=True)
)
w = iw.World(
title="Health System",
trackedItems=[health],
triggerEvents=triggers,
)
Dataclass reference[edit]
World[edit]
The top-level container. All fields have defaults — iw.World() is valid.
| Field | Type | Default | Description |
|---|---|---|---|
title |
str |
"" |
World title |
description |
str |
"" |
Description shown before starting world |
background |
str |
"" |
Background shown to the player at the start of the first turn |
instructions |
str |
"" |
Main instructions for the AI |
authorStyle |
str |
"" |
Writing style guidance |
firstInput |
str |
"" |
What should happen on the first turn |
objective |
str |
"" |
The player's objective |
skills |
list[str] |
[] |
List of skills. If empty, when the JSON is imported a generic skill will be created. |
possibleCharacters |
list[PossibleCharacter] |
[] |
Playable characters |
triggerEvents |
list[TriggerEvent] |
[] |
Trigger events |
trackedItems |
list[TrackedItem] |
[] |
Tracked items |
instructionBlocks |
list[InstructionBlock] |
[] |
Extra instruction blocks |
loreBookEntries |
list[LoreBookEntry] |
[] |
Keyword instruction blocks (internally called lorebooks) |
NPCs |
list[NPC] |
[] |
Other characters (internally called NPCs) |
victoryCondition |
VictoryDefeatCondition |
None |
Victory condition |
defeatCondition |
VictoryDefeatCondition |
None |
Defeat condition |
charSelectText |
str |
None |
Text shown on character selection screen |
descriptionRequest |
str |
"" |
Custom description instructions |
summaryRequest |
str |
"" |
Custom summarization instructions |
designNotes |
str |
None |
Design notes |
InstructionBlock[edit]
An extra block of instructions for the AI.
| Field | Type | Default | Description |
|---|---|---|---|
name |
str |
required | Block name |
content |
str |
required | Block content |
id |
str |
auto | Unique ID |
selectedAIProfiles |
list[str] |
None |
If set, only active for these AI models |
PossibleCharacter[edit]
A playable character the player can choose.
| Field | Type | Default | Description |
|---|---|---|---|
name |
str |
required | Character name |
description |
str |
"" |
Character description |
portrait |
str |
"" |
Portrait image URL |
portraitPromptDetails |
PortraitPromptDetails |
all empty | Image generation hints |
fullSizePortrait |
str |
"" |
Full-size portrait URL |
portraitOptions |
list[str] |
[] |
Alternative portrait URLs |
fullSizePortraitOptions |
list[str] |
[] |
Alternative full-size portrait URLs |
currentPortraitIndex |
int |
0 |
Selected portrait index |
characterId |
str |
auto | Unique character ID |
skills |
dict[str, int] |
{} |
Skill name to value mapping |
initialTrackedItemValues |
list[InitialTrackedItemValue] |
[] |
Per-character starting values for tracked items |
TrackedItem[edit]
A value tracked throughout the game (stats, inventory, flags, etc.).
| Field | Type | Default | Description |
|---|---|---|---|
name |
str |
required | Display name |
id |
str |
auto | Unique ID |
dataType |
TrackedItemDataType |
TEXT |
Data type (TEXT, NUMBER, XML)
|
visibility |
TrackedItemVisibility |
EVERYONE |
Who can see it (EVERYONE, AI_ONLY, PLAYER_ONLY, HIDDEN)
|
description |
str |
"" |
Description of the item |
updateInstructions |
str |
"" |
Instructions for the AI on how to update |
initialValue |
str |
"" |
Starting value |
initialValueBasedOnPC |
TrackedItemInitialValueSource |
SAME |
Whether value varies by character (SAME, CHARACTER, PLAYER)
|
autoUpdate |
bool |
False |
Whether the AI auto-updates this item |
InitialTrackedItemValue[edit]
A per-character starting value for a tracked item. Used inside PossibleCharacter.initialTrackedItemValues.
| Field | Type | Default | Description |
|---|---|---|---|
id |
str |
required | ID of the TrackedItem this overrides |
name |
str |
required | Name of the TrackedItem |
initialPCValue |
str |
"" |
Starting value for this character |
visibility |
TrackedItemVisibility |
AI_ONLY |
Visibility override |
initialValueBasedOnPC |
TrackedItemInitialValueSource |
CHARACTER |
Source mode |
NPC[edit]
Other characters, internally called NPCs.
| Field | Type | Default | Description |
|---|---|---|---|
name |
str |
required | Character name |
id |
str |
auto | Unique ID |
detail |
str |
"" |
Detailed backstory / personality |
one_liner |
str |
"" |
Brief summary |
appearance |
str |
"" |
Physical appearance description |
location |
str |
"" |
Where they can be found |
secret_info |
str |
"" |
AI-only secret information |
names |
list[str] |
[] |
Name variations / aliases |
img_appearance |
str |
"" |
Image generation appearance prompt |
img_clothing |
str |
"" |
Image generation clothing prompt |
LoreBookEntry[edit]
A keyword-activated lore entry injected when keywords appear in context.
| Field | Type | Default | Description |
|---|---|---|---|
name |
str |
required | Entry name |
content |
str |
required | Entry content |
id |
str |
auto | Unique ID |
keywords |
list[str] |
[] |
Triggering keywords |
VictoryDefeatCondition[edit]
A win or loss condition.
| Field | Type | Default | Description |
|---|---|---|---|
condition |
str |
required | Condition description for the AI |
text |
str |
required | Text shown to the player |
alreadyFired |
bool |
False |
Whether this has already triggered |
TriggerEvent[edit]
An event that fires when conditions are met, applying effects.
| Field | Type | Default | Description |
|---|---|---|---|
name |
str |
required | Trigger name |
id |
str |
auto | Unique ID |
triggerEffects |
list[TriggerEffect] |
[] |
Effects to apply |
triggerConditions |
list[TriggerCondition] |
[] |
Conditions to check |
advancedLogic |
bool |
None |
Enable nested AND/OR logic |
triggerOnStartOfGame |
bool |
None |
Fire at game start |
canTriggerMoreThanOnce |
bool |
None |
Allow repeat firing |
TriggerEffect[edit]
A single effect inside a trigger.
| Field | Type | Default | Description |
|---|---|---|---|
type |
EffectType |
required | Effect type |
data |
Any |
required | Effect payload (str, dict, list, or bool depending on type) |
id |
str |
auto (uuid) | Unique ID |
trackedItemID |
str |
None |
Related tracked item, if applicable |
TriggerCondition[edit]
A single condition inside a trigger.
| Field | Type | Default | Description |
|---|---|---|---|
data |
Any |
required | Condition payload (int, str, list, or dict depending on type) |
id |
str |
auto (uuid) | Unique ID |
type |
ConditionType |
None |
Condition type (omit for logic groups) |
category |
str |
None |
"condition" or "logic"
|
trackedItemID |
str |
None |
Related tracked item |
inequality |
Inequality |
None |
Comparison operator for tracked item conditions |
operator |
LogicOperator |
None |
AND / OR for logic groups
|
Enums reference[edit]
| Enum | Values |
|---|---|
TrackedItemDataType |
TEXT, NUMBER, XML
|
TrackedItemVisibility |
EVERYONE, AI_ONLY, PLAYER_ONLY, HIDDEN
|
TrackedItemInitialValueSource |
SAME, CHARACTER, PLAYER
|
TrackedItemAction |
SET, ADD, SUBTRACT, REPLACE
|
Inequality |
IS_EXACTLY, AT_LEAST, AT_MOST
|
EffectType |
SHOW_MESSAGE, TELL_AI, CHANGE_MAIN_INSTRUCTIONS, MODIFY_INSTRUCTION_BLOCK, CHANGE_AUTHOR_STYLE, CHANGE_DESCRIPTION_INSTRUCTIONS, CHANGE_OBJECTIVE, CHANGE_VICTORY_CONDITION, CHANGE_DEFEAT_CONDITION, CHANGE_PC_NAME, CHANGE_PC_DESCRIPTION, CHANGE_PC_SKILL, SET_TRACKED_ITEM_VALUE, FIRE_RANDOM_TRIGGER, MODIFY_KEYWORD_BLOCK, ENDS_GAME, MODIFY_TRACKED_ITEM_DETAILS
|
ConditionType |
ON_TURN, ON_EVENT, ON_CHARACTER, ON_TRACKED_ITEM, ON_RANDOM_CHANCE, PREREQS
|
LogicOperator |
AND, OR
|