A guide for LLMS
SmGF Generic Mod Reference
Purpose: Reusable reference for any Sexting my GF (SmGF) mod
Source baseline: Official Unzipped Games SmGF Modding Docs v1.0.0 and official SmGF Modding Tools
Official sources: Modding documentation (https://docs.unzipped.games/) · Script-to-Ink tools (https://tools.unzipped.games/)
Replace values written as <PLACEHOLDER> with values for your mod. Sections labeled Official behavior summarize documented SmGF behavior. Sections labeled Recommended practice are safe project conventions, not engine requirements.
- Standard folder structure
Official behavior
<MOD_FOLDER>/ ├── mod.json ├── Characters/ │ ├── player.json │ └── contact.json ├── Conversations/ │ └── <CHAPTER_OR_ARC>/ │ ├── <STORY>.ink │ ├── <STORY>-settings.json │ └── Side Stories/ │ ├── <SIDE_STORY>.ink │ └── <SIDE_STORY>-settings.json ├── Injections/ │ └── <CHAPTER_OR_THEME>/ │ ├── <INJECTION>.ink │ └── <INJECTION>.json ├── Images/ │ ├── header.png │ ├── contact.png │ ├── <CHAT_IMAGE>.png │ ├── ProfilePictures/ │ └── TheGram/ ├── Videos/ │ └── <CHAT_IMAGE>.mp4 └── TheGram/ ├── Profiles/ └── <POST>.json
Conversation folders may use any meaningful names. Only conversation content uses .ink. Characters, conversation settings, injection configurations, and mod metadata remain JSON.
The official docs recommend studying or copying the bundled Twin Problems example mod as a starting point.
- Mod manifest
Official behavior
Create mod.json in the mod root:
{ "modId": "<unique-lowercase-id>", "modName": "<Mod Display Name>", "version": "1.0.0", "author": "<Author Name>", "description": "<Short description>", "headerImage": "header.png" }
- modId must be unique, lowercase, and contain no spaces.
- Do not change modId after release; doing so breaks compatibility with existing saves.
- version should use semantic versioning.
- headerImage is optional. Recommended size: 1600×400.
Recommended practice
- Patch release (1.0.1): corrections that preserve structure and saves.
- Minor release (1.1.0): compatible new stories or features.
- Major release (2.0.0): intentionally incompatible restructuring.
- Keep released IDs stable even if files or folders are reorganized.
- Character/contact JSON
Official behavior
Create one JSON file per character in Characters/:
{ "isMainCharacter": false, "contactID": "<contact_id>", "contactName": "<Full Name>", "contactNickname": "<Display Name>", "contactNicknameShort": "<Short Name>", "profilePicturePath": "<contact-image>.png" }
- Exactly one character may use "isMainCharacter": true.
- contactID is the stable identifier referenced by other files.
- Recommended profile-picture size: 832×832.
Contact information can be changed during a conversation:
EXTERNAL SetContactDescription(contactId, newDescription) EXTERNAL SetContactPersonality(contactId, newPersonality) EXTERNAL SetContactHistory(contactId, newHistory)
~ SetContactDescription("<contact_id>", "<New description>") ~ SetContactPersonality("<contact_id>", "<New personality>") ~ SetContactHistory("<contact_id>", "<New history>")
These functions apply immediately when called.
Recommended practice
- Use lowercase IDs without spaces, such as alex, jordan, or casey_roommate.
- Never reuse one ID for two characters.
- Treat contactID as permanent after the first playable build.
- Standard conversation pair
Ink file
EXTERNAL GetStoryFlag(flagName) EXTERNAL SetStoryFlag(flagName) EXTERNAL RemoveStoryFlag(flagName)
-> start
== start Hey! Are you busy?
- Not at all. What's up? ~ SetStoryFlag("<contact_id>_received_warm_reply") -> warm_reply
- A little. Is it urgent? ~ SetStoryFlag("<contact_id>_received_guarded_reply") -> guarded_reply
== warm_reply I wanted to ask you something. -> END
== guarded_reply It can probably wait. -> END
Settings file
Create a matching <STORY>-settings.json:
{ "storyId": "<unique-story-id>", "contactID": "<contact_id>", "nextStoryId": null, "isStartingStory": true, "forceTimeInHours": 10, "passTimeInMinutes": 30, "timeIsExact": true, "forceDay": 0, "isSideStory": false }
Official behavior
- storyId: unique conversation identifier.
- contactID: contact associated with the conversation.
- nextStoryId: next conversation ID, or null when none follows.
- isStartingStory: whether this starts the mod.
- forceTimeInHours: hour using the 24-hour clock.
- The official docs describe passTimeInMinutes as the minute component when used with the forced hour.
- timeIsExact: exact versus relative time behavior.
- forceDay: 0 none, 1 Monday, through 7 Sunday.
- isSideStory: whether the conversation is interrupting side content.
- Ink structure and choices
Official behavior
- Knot: == knot_name
- Choice: * Choice text
- Divert: -> knot_name
- End: -> END
Special choice forms:
-
!Important decision -> result
-
%Internal action not sent as a message% -> result
-
<Button label> Longer message actually sent -> result
- ! marks an important choice.
- %...% creates an action choice that is not sent as a chat message.
- <Button label> Sent message separates the displayed option from the sent text. The label inside angle brackets must contain at least four characters.
Recommended practice
- Give every knot a descriptive, unique name.
- Keep irreversible state changes next to the choices that cause them.
- End every possible branch explicitly or divert it to another valid knot.
- Love, NTR, and NTRS route indicators
Official behavior
Love/NTR/NTRS markers are player-facing indicators on important choices:
- !(love) Choose the Love-focused response -> love_path
- !(ntr) Choose the NTR-focused response -> ntr_path
- !(ntrs) Choose the NTRS-focused response -> ntrs_path
- !(love,ntrs) Choose a mixed Love/NTRS response -> mixed_path
- (love) marks Love-route content.
- (ntr) marks netorare-route content.
- (ntrs) marks netorase-route content.
- Markers work only with important (!) choices.
- Markers can be combined and can accompany suppressed choices.
Route markers do not automatically create persistent route state.
- Persistent flags and branching
Official behavior
Declare the functions before using them:
EXTERNAL GetStoryFlag(flagName) EXTERNAL SetStoryFlag(flagName) EXTERNAL RemoveStoryFlag(flagName)
Set, test, and remove flags:
~ SetStoryFlag("<contact_id>_knows_secret")
{ - GetStoryFlag("<contact_id>_knows_secret"): You already told me that. -> remembered - else: What aren't you telling me? -> unaware }
~ RemoveStoryFlag("<contact_id>_waiting_for_reply")
Flags containing a character name appear under that character in Insight Mode. A flag without a recognized character name may appear under the first loaded character.
Recommended practice
Use descriptive names:
<contact_id>_knows_secret <contact_id>_relationship_changed route_love_locked route_ntr_locked route_ntrs_locked chapter_02_completed
- Use route labels for player guidance and flags for actual logic.
- Treat the documented flag system as boolean/presence-based.
- Represent levels with exclusive bands such as _low, _medium, and _high unless a different mechanism has been verified in-game.
- Remove temporary flags when their condition expires.
- Waits and typing behavior
Official behavior
<wait-3> <fake-type-4>- <wait-N> pauses the conversation for N seconds.
- <fake-type-N> displays typing for N seconds, then stops without sending a message.
Recommended practice
- Use short waits for ordinary replies.
- Use longer waits before difficult replies or image reveals.
- Use fake typing for an intentionally abandoned response, not before every message.
- Images and video
Official behavior
<wait-3> <contact-at-beach> <player-reply-photo>- <image-name> sends an image from the contact.
- Player-sent image names use the player- prefix.
- Recommended chat-image size: 832×1216.
- Use descriptive filenames without spaces; hyphens are recommended.
A video can be paired with an image:
Images/contact-at-beach.png Videos/contact-at-beach.mp4
The basenames must match exactly. Clicking the displayed image plays the matching MP4.
Recommended practice
- Use unique names such as <contact>-<chapter>-<scene>-01.png.
- Check spelling and capitalization across Ink, image, and video filenames.
- Treat the image as the video's preview/fallback.
- Side stories
Official behavior
A side story interrupts a main conversation and returns to its previous position when complete.
- Put the side-story .ink and settings JSON in Side Stories/.
- Set "isSideStory": true in its settings.
- Trigger it from another conversation:
<side-story-<SIDE_STORY_ID>>
Example:
<side-story-friend_interruption>
Recommended practice
- Use side stories for actual interruptions, not ordinary branches.
- Set prerequisites before triggering the side story.
- Set outcome flags inside it before -> END.
- Test that the correct main-story position resumes afterward.
- Injections and InjectorOS
Official behavior
Enable InjectorOS from Ink:
EXTERNAL RunCommand(command) ~ RunCommand("install-injector")
Create matching files in Injections/:
<INJECTION>.ink <INJECTION>.json
Injection configuration:
{ "injectionId": "<injection-id>", "conversationsToReceive": [ { "contactId": "<receiver_id>", "senderId": "<sender_id>", "inkFilePath": "<INJECTION>.ink" } ] }
Trigger it with:
<inject-<injection-id>>
Injection Ink supports messages, images, waits, special commands, and flag-based branching. InjectorOS automatically selects the first choice, so only one choice may appear in each injection knot.
Recommended practice
- Treat injections as deterministic NPC-to-NPC playback.
- Never put a meaningful player choice inside an injection.
- Select the appropriate linear branch using flags near the injection's start.
- Install InjectorOS once at a deliberate early point.
- Custom transitions
Official behavior
EXTERNAL ShowCustomTransition(title, subtitle)
== show_transition ~ ShowCustomTransition("Chapter Two", "Later that evening") -> next_scene
ShowCustomTransition creates a chapter or time-jump transition.
External functions execute immediately when their knot loads, regardless of dialogue or <wait> commands written earlier in that knot. The official docs therefore recommend isolating timing-sensitive transitions in their own knots.
Recommended practice
- Put each major transition in a dedicated knot.
- Divert into that knot only after the preceding conversation is complete.
- Script-to-Ink workflow
Official behavior
The official Script to Ink Converter (https://tools.unzipped.games/) converts simple scripts into linear Ink. Its output is intended as a starting point for later editing.
contact: NPC dialogue mc: Player response mc: Choice one|Choice two|Choice three mc: First message; second message; third message mc: Go to another section->SectionName contact: Dialogue then divert->SectionName mc: Choice A->1|Choice B->2 contact->1: Temporary branch dialogue
The converter supports named tabs/knots, temporary split paths, chained player messages, standard external headers, a start redirect, -> END, group-chat prefixes, random waits, generated knot names, gather-based output, and project export/import as JSON.
Recommended workflow
-
Draft dialogue and broad choices in the converter.
-
Export a converter project backup.
-
Convert the draft to Ink.
-
Add meaningful knots and real branching manually.
-
Replace random waits with intentional pacing.
-
Add route indicators and state flags.
-
Add media, side stories, injections, and isolated transitions.
-
Create the matching settings JSON.
-
Validate and test all routes in-game.
-
Packaging and installation
Official behavior
SmGF documents three installation methods:
- Online Mod Browser: curated mods are downloaded through the in-game Mods app. Authors contact a Discord moderator for possible inclusion.
- Direct URL: the URL must point to a .zip; the game downloads it and applies security checks.
- Drag and drop: place the unpacked folder in the Mods folder, refresh, and load it.
Loaded mods replace the base-game experience and use separate saves. Restart the game completely to return to the base game.
The official docs recommend zipping the mod folder and testing the real archive through the URL downloader before sharing it.
Update-behavior caveat
The official documentation reviewed for this reference does not define automatic update notifications, version-comparison rules, in-place upgrade behavior, or removal of obsolete files during an upgrade. The version manifest field is documented, but do not assume a particular upgrade mechanism without testing the current game build or confirming it with Unzipped Games.
Recommended practice
- Package one complete mod root with mod.json at the expected top level after extraction.
- Keep modId, contactID, storyId, injection IDs, and released flags stable.
- Increment version for every public archive.
- Include release notes and warn users about any save incompatibility.
- Test both an unpacked folder and the final downloadable ZIP.
- Generic validation checklist
[ ] mod.json is valid JSON. [ ] modId is unique, lowercase, stable, and contains no spaces. [ ] The public version number was incremented. [ ] Exactly one character has isMainCharacter: true. [ ] Every contactID is unique and resolves correctly. [ ] Every conversation has its matching settings JSON. [ ] Every storyId is unique. [ ] Every nextStoryId resolves or is null. [ ] Every used external function is declared in its Ink file. [ ] Every Ink branch reaches a valid knot or -> END. [ ] Love/NTR/NTRS markers are used only on important choices. [ ] Route markers match the content and persistent route state uses flags. [ ] Temporary flags are removed when no longer applicable. [ ] Side stories have isSideStory: true and return correctly. [ ] Injection knots contain no more than one choice. [ ] Image references resolve and player images use player-. [ ] Video/image basenames match exactly. [ ] Transitions occur at the intended time from isolated knots. [ ] Forced hour, minute, and day behavior was tested in-game. [ ] Every route and choice was playtested. [ ] The in-game validator reports no errors. [ ] Reload testing succeeds. [ ] The final ZIP installs and loads through the intended distribution method.
- Blank project information
Mod name: Mod ID: Current version: Author: Starting story ID: Main-character ID: Primary contact IDs: Supported routes: Required game version: Distribution URL: Save-breaking changes: Known limitations:
Maintenance note
SmGF is still in development. Re-check the official documentation (https://docs.unzipped.games/) before publishing a major release, especially for manifest fields, installation/update behavior, and newly supported phone apps.
