Content tags and distributions
The closed vocabulary that says what a mod, a feature or a scene contains, and the distribution profiles (Steam, Underlives.com) that decide where it ships.
Format in progress, engine in build (2026-10-25). The tags and the two files below are read by the engine being built; today's game ignores them, the validator and the packaging will not. Write them now, they will not change.
The game exists in more than one distribution: a Steam build, with what Steam accepts, and a full build on the hub and underlives.com. What goes where is carried by the data, checked by the validator, and never kept as a hand-written list of mods.
The vocabulary
ul_Base/Content/content_tags.json holds the closed list, each tag with its doc. A mod may add a tag (a new entry in its own Content/content_tags.json); nobody removes one.
| Tag | Covers |
|---|---|
sexual |
Explicit sex on screen, or a mod whose subject is sex (a brothel, a strip club, swinging, porn work). |
nonconsent |
Sexual acts without consent, or sexual pressure on someone who cannot refuse: drugged, unconscious, blackmailed, captive. |
incest |
Sexual or romantic acts between relatives. |
violence |
Physical harm shown or dealt: fights, beatings, weapons, abuse, killing. |
drugs |
Drug use, dealing or spiking shown. |
bestiality |
Sexual acts with animals. |
gore |
Mutilation, corpses or wounds in detail. |
fantasy |
Creatures, magic, horror, time loops: outside the realistic town. Not sensitive by itself; every build keeps it. |
An element without a tag is for everyone. The children rules (the engine's own, Reference/CHILDREN_POLICY.md in the game repository) are not a tag: the engine enforces them in every distribution, and no profile lifts them.
Where a tag goes
| On | How | Example |
|---|---|---|
| The whole mod | MODULE_CONTENT: tag, tag in the .ulmod |
MODULE_CONTENT: violence, drugs |
| A feature | The fourth field of its MODULE_FEATURE line |
MODULE_FEATURE: the_wife | Taking the debtor's wife | on | nonconsent, sexual |
| A scene | content = { … } at the top of the scene table |
content = { "drugs" } |
| An action | content = { … } in the .ulaction |
content = { "drugs" } |
| An asset pack | content in its manifest |
a clothing pack of fetish wear: { "sexual" } |
An element's tags are the union of its own, its feature's and its mod's. So a mod carries the union of everything in it, and a scene that goes further than the rest of the mod carries the difference itself: ul_Debts is violence throughout, and the one scene where a collector drugs the debtor adds drugs on that scene.
The scene grain is what makes a partly touched mod shippable: the mod stays, the scenes that carry a refused tag go.
What implies a tag
Some things a scene does say what it contains without anyone writing a tag. The implies table of content_tags.json maps them to tags, and the validator reads it:
| Key | Means | Implies |
|---|---|---|
witness.<type> |
The scene records this witness act type | its tags: sexual_assault → nonconsent, sexual; assault → violence; drug_deal → drugs; body_disposal → violence, gore… |
call.Game:Sex, call.Game:startSexScene |
A sex scene plays | sexual |
call.Game:TalkNonConsensual, call.Actor:setForcedParticipant |
The sex scene is forced | nonconsent, sexual |
call.Actor:knockOut, call.Actor:die |
Someone is knocked out or killed | violence |
effect.sex, effect.sex.forced |
The effects of a table scene | sexual; nonconsent, sexual |
A scene that implies a tag its mod does not carry is a validator error: the author either adds the tag or takes the act out. A scripted scene (script, or a legacy RUN function) is read for its calls; a table scene for its effects. The full table has one line per witness act type of ul_Base and of every shipped mod.
Distributions
ul_Base/Content/distributions.json: steam with name and deny, full with an empty deny.
| Profile | Refuses |
|---|---|
steam (Steam) |
nonconsent, incest, bestiality |
full (Underlives.com) |
nothing |
deny lists the tags a build refuses. The Steam list follows Steam's adult-content rules of the day and is data, not code: it changes in this file, never in the engine.
What the engine and the tools do with it
- Packaging.
release.ps1 -Distribution steamleaves out a mod whose own tags hit the deny list, and from a partly touched mod removes the features, scenes and actions that carry a refused tag. Before packaging, the validator checks that what remains stands: no kept scene points at a removed one, a quest with a removed step is removed whole. - Runtime. The build's distribution is read-only (
Game:distribution()→"steam"or"full"). Refused elements are not disabled, they are absent from the disk. - The Mods menu. A mod installed from the hub into a Steam build with a refused tag does not load; the list shows it as Blocked with the reason ("carries nonconsent, refused by this distribution"), like a missing requirement today.
- The hub. Shows each mod's tags and its compatibility per distribution, and refuses a publication without tags when the validator infers some.
- The validator (
Tools/ModLint/) checks the vocabulary (an unknown tag is an error), theimpliesrules, and that a scene's owncontentis a subset of what the vocabulary knows.
This is not a player setting or a parental filter. The Peaceful / Balanced / Dark world profiles are a game setting; the distribution is a fact of the build.
A complete example
ul_Debts: loan sharks, collectors, beatings, one feature that goes further, one scene where a debtor is drugged before a "lesson".
iniMODULE_UNIQUEID: ul_Debts
MODULE_NAME: Debts
MODULE_REQUIREMENTS: ul_Base
MODULE_CONTENT: violence
MODULE_FEATURE: loan_sharks | Loan sharks | on
MODULE_FEATURE: bookie | The bookie | on
MODULE_FEATURE: the_wife | Taking the debtor's wife | on | nonconsent, sexual
The one scene that goes further than the mod (Scenes as tables):
luareturn {
id = "ul_db_press_lesson",
feature = "loan_sharks",
content = { "drugs" },
beats = { --[[ … ]] },
}
What each build does:
| Build | ul_Debts | ul_db_press_lesson |
feature the_wife |
|---|---|---|---|
| full | loads | plays | on |
| steam (deny nonconsent, incest, bestiality) | loads: violence and drugs are accepted |
plays | left out of the package |
Change the steam profile to deny drugs and the same data gives: ul_Debts loads, ul_db_press_lesson is left out too, and the validator first checks nothing kept still points at it.
A mod whose subject is refused is simply absent from that build: ul_Incest (sexual, incest, nonconsent) does not exist in the Steam package, and a Steam player who installs it from the hub sees it Blocked in the Mods menu with the reason.
Shipped mods today
The inventory of 2026-10-25 tagged 73 of the 166 shipped ul_* mods (Reference/CONTENT_TAGS_INVENTORY_2026-10-25.md in the game repository): every tagged mod carries a MODULE_CONTENT: line, and the report names the scenes that carry the strongest tag inside mixed mods, which is where the scene-level content goes as those mods are converted to tables.