Files
Naked-Desire/PLAN.md
T
2026-06-03 21:42:24 +03:00

516 lines
88 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Development Plan
Working document for Naked Desire implementation. Tracks current state vs. the GDD (README.md) and the phased roadmap. Update this file as state changes — code is the source of truth, this file is the navigation chart.
When the GDD changes, update README.md first, then revisit this plan. When this plan changes, keep phase exit criteria concrete so we know when to move on.
---
## 0. Vertical Slice (First Playable Demo) — milestone plan
> Added 2026-05-30. This is the **active near-term target** and supersedes the linear Phase 1→10 ordering below as the working priority. The phased roadmap (§3) is still the long-range map; this section re-prioritizes it toward one goal: a playable build that proves the core loop is fun before we invest in breadth.
### 0.1 Status re-audit (the §1 snapshot below is stale)
A pass over the actual tree on 2026-05-30 shows the code is **well ahead** of §1. Corrections (verified by reading source, not inferred):
- **Phase 1 — done.** `EClothingSlotType` is the locked 18-slot list (`Clothing/ClothingSlotType.h`). Item identity is a clean `UItemDefinition`/`UItemInstance` split with `CreateInstance`, `CreateFromRecord`, `ToSaveRecord` and stable GUIDs (`Items/`). `UClothingItemDefinition` dropped `IsUnderwear`/`IsRestrictive` and now carries a `Restrictions` list (`FClothingRestriction` + `ERestrictionType`), `CanExpose`, `ContainerSlots`, `CoveredBodyParts`, `HiddenBodyParts`. Toy items exist (`Items/SexToyItem` + `SexToyInstance`). `STARTING_MONEY` is applied at new-save creation; `Money` lives on the save.
- **Save — rearchitected, the "empty save" bug is gone.** `UGlobalSaveGameData` is now the durable state container with granular `Add/Update/Remove` for `WardrobeItems`/`EquippedItems`/`WorldItems` (all `FItemSaveRecord`). `USaveSubsystem::GetCurrentSave` creates + seeds a new save from `StartingSaveData`. `GameMode::BeginPlay` rehydrates world `AItemPickup`s from records with transforms. Round-trip architecture is in place.
- **Phase 2 — done.** `Interactables/ItemPickup` is the world item actor; drop/pickup + interaction (`Interaction/InteractionComponent`) exist.
- **Phase 3 — done.** `SessionManagerSubsystem` + `SessionLossResolver` as previously noted.
- **New since snapshot:** `Censorship/CensorshipComponent`, `Clothing/ClothingVisualsComponent` (runtime per-slot mesh spawning) + `ClothingSlotsData`, GASP-style movement enums (`Global/Gait|Stance|AirMode|MovementState`), and a full **UI layer** (`UI/HUDWidget`, `GameLayoutWidget`, `Inventory/*` panels, **`RadialMenu/*` = the §14.1 quick-action menu**).
- **Minor spec drift to fix when convenient:** `ERestrictionType` spells `BlockPhoneUse` as `BlockPhoneUsage` and splits the GDD's single `BlockExposeAction` into `BlockBoobsExpose`/`BlockVaginaExpose`/`BlockAnalExpose`. `FClothingRestriction` fields lack `EditDefaultsOnly` so they aren't author-editable in the asset. Not blocking the slice.
§1 should be rewritten in a later pass; until then, trust §0.1 over §1 where they disagree.
### 0.2 What the slice must prove
One question: **is the risk-vs-reward exhibition loop (Pillar 1) fun, moment to moment?** Everything else is breadth we can add once that's validated. The slice is the smallest build that lets a playtester feel the core tension end-to-end:
> From a cold save: wake in the apartment → dress at the wardrobe → accept a commission from the forum → step outside (**session starts**) → walk a populated street where NPCs observe you and **coverage-weighted embarrassment, lust, and pulse climb** → complete the commission's public-exposure objective → **money / XP / followers credit instantly** → either walk home safely (embarrassment decays, day ends, **autosave**) or fail the session (embarrassment-max / energy-zero / police) and get resolved home per §4.4. Sleep advances the day; the run survives save → quit → reload.
If that loop reads as tense and rewarding on a greybox street, the design is proven. If it doesn't, no amount of casino/phone/path breadth will save it — so we find out now.
### 0.3 Cut list (explicitly OUT of the slice)
Deferred to post-slice phases. Do **not** build these for the demo:
- Full phone & forum stack — battery, gallery, livestream, Feetex, maps, bank app, health tracker, phone models, Electronics Shop (§9, §9.8, §9.9, Phase 8). Replace with a **minimal commission-board widget** + the existing attribute HUD.
- 90-day calendar, weekly rent, eviction, endless mode, weekly missions, weekly follower auto-deposit (§2.4, §15.2, Phase 5). Slice uses a bare day counter only.
- Casino, gym, beauty salon, café, convenience store, adult shop (§10.4, Phase 10). Wardrobe buy-flow already covers "acquire clothing."
- GAS migration (§17.2, Phase 4) — keep the bespoke `StatsManager`.
- Procedural commission generation at scale, path-requirement filtering, `failurePenalty` tiers (§13.4, Phase 7) — slice hand-authors ~610 templates.
- Recognition→news pipeline, wanted posters, theft, rip & tear, restraint unlock minigame, food/cooking, bags, underwear selling, XP-spend/path-level UI, multiple phone tiers.
- Toys beyond the data model already present.
### 0.4 Core code workstreams (priority order)
Each `VS-n` is a vertical task with a concrete exit check. VS-1 is the gate — it unblocks the entire risk side and should land first.
- **VS-1 — Coverage → embarrassment (THE dial). DONE (2026-05-30).** `ClothingManager::GetEffectiveCoverage(EBodyPart)` implements §6.3.2 (`max()` across covering garments; absent part = 0; fixed an earlier bug where non-covering garments reported full coverage and an empty equip set crashed `FMath::Max`). The `0.0f` stub in `StatsManager::TickComponent` is gone. Observation is now **per-observer directional**: `ANakedDesireCharacter::ComputeObservedExposure` reuses the existing two-trace (`boobs_root` / `pelvis`) LOS logic and sums `(1 - coverage)` over the revealing parts each observer can actually see; `CanBeSeenFrom` shares that helper (revealing = coverage below `ObservationRevealThreshold`, default 0.9 — replaces the old binary `IsBodyPartExposed` gate so revealing-but-not-nude clothing now ticks). `StatsManager` holds the live observer set (`SetObserved(bool, AActor*)`), re-traces each at 1Hz, normalizes to `[0,1]`, and applies a saturating crowd multiplier `1 + ObserverDensityScale * ln(N)`. Per-part weights are uniform for now. Active expose state (§6.3.6) is **not** yet folded into coverage — that's VS-3. Day/night and recognition multipliers still stubbed to `1.0`. **Exit:** standing nude in front of an NPC raises embarrassment visibly faster than standing fully clothed; per-garment coverage changes the rate monotonically.
- **VS-2 — Lust + Pulse (minimal).** Add `Lust`/`MaxLust` and `Pulse`/baseline to `StatsManager`. Passive lust gain; pulse rises on run / exposure events / observed-while-exposed and decays at rest; pulse multiplies embarrassment **and** lust gain (§7.5). Masturbate quick-action resets lust. **Gating (§23 #25): home masturbation is always available; in-session masturbation is Slut-path-gated.** For the slice the in-session entry can ship open if path investment isn't wired yet, but keep the home-vs-session split in mind so it's a config flip later, not a rewrite. **Exit:** the three attributes visibly interact in a playtest — running spikes pulse, which accelerates embarrassment gain; masturbating drops lust.
- **VS-3 — Expose action.** Radial-menu entry per equipped garment exposing the parts in its `CanExpose`; temporarily counts those parts as uncovered for VS-1 math; blocked when another garment covers the same part (§6.3.6). Reuses the existing `RadialMenu` + `ClothingManager`. **Exit:** flashing a coat in front of an NPC produces an embarrassment spike that ends when the expose ends.
- **VS-4 — Commission accept + instant reward.** Add the Accept lifecycle (commit on accept, no penalty when un-accepted) and **instant reward crediting on `OnMissionCompleted`** — money to the save, XP to the shared pool, followers stub (§13.1/§13.2/§23 #23). `MissionsManager` currently completes a mission but credits nothing (`MissionsManager.cpp:27-32`) — wire crediting here. Ensure the typed goals the slice needs exist (`BeFullyNaked`, `BeFullyNakedNearNPCs`, `ExposeBodyPart` — partially covered by `FlashGoal`/`ExposeBodyPartRestriction`). Surface it through a **minimal forum/board widget** (accept + active-objective tracker) reusing the existing UI layer. **Exit:** accept a commission, satisfy it in the world, see money/XP/followers tick up at the moment of completion with no return-home step.
- **VS-5 — NPC types + the session-loss threat. IN PROGRESS.** Differentiate at least **Walker / Stalker / Blogger** by observation weight + behavior (Stalker = sustained stare, high embarrassment; Blogger = photo → recognition stub), plus a **Snitch → Police** beat that drives a real escape: Snitch report sets a chase, Police capture routes through the existing `SessionLossResolver` (`PoliceCapture`, `bPoliceChaseActive`). This is what makes the street a risk space (Pillar 1). **Done:** the data-driven NPC type model (`ENPCType`, `UNPCTypeDefinition`, `ANPC` accessors) and the Walker/Stalker mechanical split via per-observer observation weight in `StatsManager` (see §1.3). **Remaining:** author the `DA_Walker`/`DA_Stalker` assets + BT branch on `ShouldStopToObserve`; then Blogger (recognition stub) and the Snitch→Police chase. **Exit:** the three civilian types feel distinct; a Snitch can trigger a police chase that ends in a §4.4 capture resolution.
- **VS-6 — Day loop.** A lightweight `TimeOfDaySubsystem` (or formalize the BP-driven `GameMode::OnHourChanged`): day phase 08:0020:00, NPC density gated by phase (fix the `0921` window in `NPCSpawner.cpp:40` and the `Hour==4` day-roll in `GameMode.cpp:54`). Sleep at the apartment bed advances the day, restores energy, and autosaves. Rent stays stubbed off. **Exit:** play a full day, sleep, wake on the next day with state intact.
- **VS-7 — Save round-trip across the loop (gate).** Validate equip/unequip, world drop, wardrobe, money, day index, and attribute snapshot survive save → quit → reload, exercising the loop end-to-end (this is the existing Phase 1 exit criterion, re-run against the live loop). **Exit:** §6.6 inventory table holds after each session-loss cause, and a mid-run reload reproduces the player's exact state.
### 0.5 Art & content track (parallel — the long pole)
Runs alongside the code track; the district + clothing set gate the first real playtest, so start them immediately. For Oleh / artist:
- **One functional district.** Apartment interior (wardrobe, bed; charger spot optional) + a contiguous block of streets/alley with built nav mesh, NPC spawn points, and 23 commission landmarks. Greybox is acceptable for the slice, but it needs working collision, lighting, and a clear "apartment door = session boundary" threshold for the `ALocationTrigger`.
- **Clothing set spanning the coverage curve.** ~812 items across slots, authored from fully-covering → revealing → underwear so the VS-1 dial is actually testable, plus 12 expose-capable garments (coat / pleated skirt) for VS-3. Skeletal meshes + materials wired through `ClothingVisualsComponent`. Author `CoveredBodyParts` values deliberately (the "just underwear" discount lives in the numbers, §6.3.2 / §23 #20).
- **Player body states.** Base body + the existing censorship meshes + a clean nude state that reads correctly with the coverage system.
- **Exhibition animation set.** Montages for expose, masturbate, photo-react idle, and embarrassment reactions; NPC reaction anims (Walker glance, Stalker stare, Blogger photo, Snitch point/call). A shared retargeted GASP set is fine for the slice — these don't need to be bespoke yet.
- **35 NPC visual variants** so a crowd doesn't look cloned.
- **Minimal UI/audio.** Commission-board + objective-tracker widget art (reuse existing `UI/` + `Audio/UI` tone set), an observation/exposure stinger, and a camera-shutter SFX for Blogger photos.
### 0.6 Sequencing, risks, and definition of done
- **Critical path:** VS-1 → (VS-2, VS-3, VS-5 in parallel) → VS-4 board → VS-6 loop → VS-7 gate. VS-6 and the art track can run from day one independently.
- **Top risks:** (1) the coverage→embarrassment dial doesn't *feel* like risk even when wired — mitigate with the debug overlay (Phase 4 note) showing live modifier sources from VS-1 onward; (2) art content is the schedule driver, not code — lock the district + clothing scope early and greybox aggressively; (3) NPC density vs. perf on a populated street (§19) — cap spawns low for the slice, defer instanced-extras LOD.
- **Definition of done:** a first-time playtester completes the §0.2 walkthrough end-to-end in one sitting on the greybox district, the risk dial legibly tracks coverage/exposure, both a safe return and at least one loss cause resolve correctly, and the run persists across a save/quit/reload. When that's true, return to §3 for breadth (phone/forum, calendar/rent, NPC/recognition depth, paths, then the Phase 10 locations).
---
## 1. Implementation status snapshot
State of the C++ module as of the latest pass. File references use `Source/NakedDesire/...` paths.
### 1.1 Implemented
- **Item identity scaffold (GDD §6.1)** — `Items/ItemInstance.h` defines `UItemInstance` as an abstract `UObject` with a stable `FGuid InstanceID`, auto-assigned in `PostInitProperties` and refreshed in `PostDuplicate`. `Clothing/ClothingItemInstance.h` inherits from it and holds per-instance `Condition`, and a pointer to the immutable `UClothingItem` definition. (The legacy per-garment `StoredItems` container array was removed per GDD §27 — only bags hold items; the item list will live on a future bag `UItemInstance` subclass.)
- **Definition / instance split (GDD §6.1, §17.4)** — `Clothing/ClothingItem.h` is the immutable `UPrimaryDataAsset` definition. Most fields align with the current GDD: `Coverage`, `CanExpose`, `ContainerSlots` (typed S/M/L via `EGarmentContainerSlotType` + `FGarmentContainerSlot`), `ProgressionPath`, plus rendering data (`SkeletalMesh`, `Materials`, `StaticMesh`). The `IsUnderwear` and `IsRestrictive` fields also exist but **no longer match the locked spec** (§20 #20, §20 #21) — see §1.4 for the removal / migration work.
- **Progression path enum (GDD §5)** — `Progression/ProgressionPath.h` defines `EProgressionPath { None, Slut, Exhibitionist, Slave }`. Used on `UClothingItem::ProgressionPath`.
- **Body-part enum (GDD §6.3)** — `Clothing/BodyPart.h` defines `EBodyPart { None, Boobs, Ass, Genitals }`. Used on `UClothingItem::CanExpose`.
- **Save scaffolding (GDD §16)** — `SaveGame/SaveSubsystem.h/.cpp` (`UGameInstanceSubsystem`), `SaveGame/ItemSaveRecord.h` (generic `{ InstanceId, Definition, Condition, ParentId }`). `Global/Constants.h` now uses `DefaultSaveSlotName` instead of a single `SLOT_NAME` macro.
- **Observation-driven embarrassment (GDD §7.1)** — `NPC/NPCAIController.cpp:56-69` listens to `OnTargetPerceptionUpdated` and forwards bSensed transitions to `Stats/StatsManager::SetObserved`. `Stats/StatsManager::TickComponent` (`StatsManager.cpp:23-38`) now gates gain vs. decay on `ObserverCount` instead of unconditional decay. Foundation for Phase 4.
- **Modular character w/ per-slot meshes** — `Player/NakedDesireCharacter.cpp:40-67` creates 14 skeletal mesh components, one per current `EClothingSlotType` value. Aligns with GDD §17.6 in spirit; the specific slot list needs reshaping per §1.4 (locked 18-slot list now lives in GDD §6.5).
- **Equip / unequip / drop event** — `Clothing/ClothingManager.cpp:88-163` (slot-based, broadcasts `OnClothingEquip / Unequip / Dropped` with `UClothingItemInstance*`).
- **Censorship toggle** — compliance feature on `NakedDesireCharacter` (`BoobL/R`, `Front/BackBottom` static meshes), driven by `UNakedDesireUserSettings`.
- **AI sight + behavior tree** — `NPC/NPCAIController.cpp` runs a BT with `Player` / `TargetLocation` / `SpawnLocation` blackboard keys. `NPC/NPCSpawner.cpp` proximity-gated spawn with day / night caps.
- **Commission system — rebuilt (`Commissions/`, slice-first foundation).** Replaces the old `MissionBuilder/` Goal/Restriction model with the §13.4 vocabulary. `UCommissionObjective` is the unified typed step (owns its condition + an optional `RequiredHoldSeconds` hold timer — "expose for N s" vs "expose once" is a data value, not a class); concrete steps derive from a shared base chain — `UCommissionObjective``UObserverObjectiveBase` (reacts to the observer count) → `UCoverageObjectiveBase` (re-evaluates on equip/unequip via `ClothingManager::GetEffectiveCoverage`). Implemented steps: `BeFullyNaked`, `ExposeBodyPart`, `BeFullyNakedNearNPCs`, `StayUnseenWhileNaked`, `GatherCrowd`, `BeObservedWhileExposed`, `WearOnlyUnderwear`, `BareRegion` (topless/bottomless), `StayBelowCoverage`, `ReachEmbarrassment`/`SustainEmbarrassment`, plus the `UTravelObjectiveBase` distance family — `RunNakedDistance`, `WalkNakedDistance`, `ExposeWhileWalking`, `WalkNakedWhileObserved` (shared clamped distance-sampling timer; subclasses only override `DoesSampleCount()`) — and `MoveDistanceFromClothing` (polls the new `UDroppedClothingSubsystem` for the nearest garment you left behind; `ReachLocationAwayFromClothing` is this + a `ULocationConstraint`, no new class). Location objectives (`EnterLocationNaked`, etc.) are authored as a coverage step + `ULocationConstraint` (no new class). `UCommission` is the `Offered→Accepted→Completed/Expired` state machine + `FCommissionReward` (money/XP/followers). `UMissionSubsystem` (`UWorldSubsystem`, like `TimeOfDaySubsystem`) offers a hand-authored `UCommissionBoardConfig` pool (set on `UNakedDesireGameInstance::CommissionBoard`), drives accept/abandon, **pays rewards instantly on completion** (money→save, XP→character; followers stubbed, Phase 8), **expires accepted commissions on `OnDayChanged`**, and persists state to a new `UGlobalSaveGameData::Commissions` bucket (id-keyed, state-level — objective mid-progress not preserved). Added `UStatsManager::GetObserverCount()` + `OnObserversChanged` so "near NPCs" reuses the embarrassment observer set. **Composition:** objectives gate on **constraints** (`UCommissionConstraint``UObservedConstraint`, `UDayPhaseConstraint`, `UWearingSlotConstraint`) — "do X while Y" with no new objective code; and a commission can set `bSequentialObjectives` to require its steps in array order (strip → walk → …). The full objective/constraint idea backlog with feasibility tags lives in `COMMISSIONS.md`. **Follow-ups:** commission board UI is in (forum app — see Phase 8 phone block); profile tab is still a placeholder; `failurePenalty` is a hook only (no reputation/followers yet); procedural generation + path-filtering + the remaining §13.4 step types (`PerformAction`, `BeObservedByNPCType`, `TakePhotoAtLocation`, `DeliverItemTo`) are Phase 7. Weekly commissions share the daily lifecycle today (re-offered + expired every day-roll), so a true week-long weekly arc — survive day-rolls, expire at week-end, retain "completed this week" — is still unbuilt (§13.1).
- **Old mission framework — parked, not deleted.** `MissionBuilder/` (`Mission`/`MissionGoal`/`GoalRestriction`, `FlashGoal`/`MinTimeGoal`, the 3 restrictions, `MissionsConfig`) and the `ANakedDesireCharacter::MissionsManager` component remain on disk but dormant; remove in a cleanup pass once the new system's UI is wired and no Blueprint references the old classes.
- **Daily-mission OOB guarded** — `NakedDesireGameMode::RefreshDailyMissions` now clamps `DaysPassed` to the authored array bounds (`NakedDesireGameMode.cpp:70-71`). Still a hand-authored list (see §1.3).
- **Location system (GDD §10.4) — unified on `ULocationSubsystem`.** `Locations/LocationSubsystem` (`UWorldSubsystem`) is the single authority on which tagged locations the player occupies: `ALocationTrigger` volumes report player enter/leave (ref-counted, so overlapping boxes of one place don't churn), and it exposes `IsPlayerInLocation(tag)` (hierarchical via `FGameplayTagContainer::HasTag`), `GetCurrentLocation()`, and `OnLocationEntered/Exited`. Locations are identified by `ULocationData::Tag` and **nest** (inside `Location.City.Beach` you also match `Location.City`). `bIsApartment` is **gone**: `USessionManagerSubsystem` now subscribes to the subsystem and starts/ends the session on the native tag `TAG_Location_Apartment` (`Global/NakedDesireGameplayTags`). Commission location gating is `Commissions/Constraints/LocationConstraint`. **Content requirement:** the apartment trigger's `ULocationData` must be tagged `Location.Apartment` (or a child); each trigger box needs overlap-with-Pawn collision.
- **Session manager (GDD §4.1–§4.4)** — `Global/SessionManagerSubsystem.h/.cpp` (`UWorldSubsystem`). Tracks `bSessionActive`, emits `OnSessionStart` / `OnSessionEnd(ESessionLossCause)`; `ESessionLossCause = { SafeReturn, EmbarrassmentMax, EnergyZero, PoliceCapture }`. Apartment `ALocationTrigger` starts a session on exit and safely ends it on re-entry. Subscribes (next-tick after world begin play) to `UStatsManager::EmbarrassmentUpdate` (max-hit → `EmbarrassmentMax`) and `EnergyUpdate` (≤0 → `EnergyZero`). Exposes `bPoliceChaseActive` (+ setter / getter) for the §4.4 loss-precedence rule the resolver owns. Replaces the old `EndGameEmbarrassed` GameMode BP call, which `UStatsManager::IncreaseEmbarrassment` no longer invokes. The `EndGameEmbarrassed` BlueprintImplementableEvent declaration still exists on `ANakedDesireGameMode` but is now dead from C++ and should be removed once BP no longer references it.
- **Session loss resolver (GDD §4.4)** — `Global/SessionLossResolver.h/.cpp` (`UWorldSubsystem`). Single entry point `ResolveLoss(ESessionLossCause)`, bound to `USessionManagerSubsystem::OnSessionEnd`. Applies the police-chase precedence override (any cause → `PoliceCapture` while `bPoliceChaseActive`), then per cause: `EmbarrassmentMax` no-cost; `EnergyZero` destroys every world `AItemPickup` + clears its save record (guaranteed sleep loss); `PoliceCapture` deducts `PoliceCaptureMoneyPenalty` if affordable else flags a holding-cell outcome; `SafeReturn` no loss. Never strips equipped clothing. Autosaves, then broadcasts `OnSessionLossResolved(FinalCause, bWentToHoldingCell)` for the BP presentation / time-skip layer. See §1.3 for the pieces still delegated to BP / later phases.
- **Movement** — `EnhancedInput`, walk / run / crouch (`NakedDesireCharacter.cpp:115-127`), stamina-gated run (`Tick` lines 91-113).
- **Wardrobe storage + management** — `Inventory/InventorySubsystem` (`UGameInstanceSubsystem`) is the single runtime owner of the off-body store. It holds live `UItemInstance`s mirrored from `UGlobalSaveGameData::WardrobeItems` and exposes the atomic moves `AddToWardrobe` / `RemoveFromWardrobe` / `EquipFromWardrobe` / `UnequipToWardrobe`, each mutating the wardrobe + equipped save buckets together and broadcasting `OnWardrobeChanged`. `AClothingManager` stays the body-state authority (owns the `EquippedItems` bucket via the new `EquipSlot` + existing `RemoveClothing`); the bodysuit exclusion rule is now the shared static `UClothingManager::GetBodysuitExcludedSlots` and routes displaced garments back to the wardrobe instead of dropping them to the world. `AWardrobe` is reduced to an interaction shell that forwards to the subsystem (its stale `ClothingItems` array is gone). UI: `WardrobeScreenWidget` inits the inventory list and hosts the `EquipmentSlotMenuWidget` popup (same plumbing as `InventoryScreenWidget`); `WardrobeInventoryWidget` renders the live list and re-renders on `OnWardrobeChanged`; clicking a wardrobe item calls `EquipFromWardrobe`. The slot menu has a single Remove button whose `Init(slot, bAtWardrobe)` flag decides the action — store via `UnequipToWardrobe` when opened at the wardrobe, drop to the world otherwise. **Home-storage model (GDD §6.5 / §10.4 / §28):** the wardrobe is the general home stockpile for **all non-food** items (clothing, sex toys, phones, keys, spare bags) — `WardrobeItems` is already a generic `UItemInstance` list, so this matches with no change. Food is **not** stored in the wardrobe; it lives in the **fridge** (separate fixture, pending — see §1.3). **Follow-ups:** `BuyItem` buy-flow not reattached (no caller yet); world pickup (`ItemPickup``TakeClothing`) still doesn't clear the `WorldItems` record; non-clothing wardrobe items (phones/toys) are stored but not yet rendered in the wardrobe UI.
### 1.2 Partially implemented (deviates from GDD)
- **Save subsystem (GDD §16)** — scaffolded but **not actually wired through gameplay**. `USaveSubsystem::SaveGame` (`SaveSubsystem.cpp:10-13`) delegates to `UGlobalSaveGameData::SaveGame`, which creates a *fresh empty* `UGlobalSaveGameData` (`GlobalSaveGameData.cpp:45-53`) and writes it — i.e., it does not capture the live player / wardrobe / world state at all. `USaveSubsystem::LoadGame` calls the static loader but discards the result without applying it (`SaveSubsystem.cpp:5-8`). `ClothingManager::HydrateClothing` (`ClothingManager.cpp:66-73`) is an empty stub with the previous logic commented out. Exit criterion of Phase 1 (round-trip an item with modified condition) is not met yet.
- **Item identity → world (§6.1)** — `UItemInstance` GUIDs exist, but no item has been promoted to a world `AActor`. `ClothingManager::DropClothing` (`ClothingManager.cpp:156-163`) still only broadcasts `OnClothingDropped` — no spawn, no parent reassignment in any registry.
- **Attributes (§7)** — `StatsManager` covers Embarrassment, Energy, Stamina with observation-driven gain now correctly tied to NPC perception. Missing: Lust, Pulse (§7.5), Recognition, Reputation, Followers, Wanted. Coverage weighting in the gain formula is **live** (VS-1, §0.4) — per-observer directional exposure replaces the old `0.0f` stub. Food-buff hookpoints (per-rate multipliers for stamina regen, embarrassment-gain resistance, lust-gain resistance — see GDD §6.7) are not in place; future Phase 10 work depends on the attribute simulation accepting external multipliers cleanly.
- **Coverage (§6.3.2)** — `ClothingManager::GetEffectiveCoverage(EBodyPart)` implements the locked `max(coverage)` across covering garments (VS-1). `ClothingManager::IsBodyPartExposed` remains binary and is still used by the censorship path; the observation/embarrassment path no longer relies on it. Active expose state (§6.3.6) is not yet folded into the coverage result (VS-3). `UClothingItem::IsUnderwear` is dead spec and should be removed during the Phase 1 cleanup.
- **Body-part enums — duplicated** — both `Player/PrivateBodyPartType.h` (`EPrivateBodyPartType { FrontBottom, BackBottom, FrontTop }`) and `Clothing/BodyPart.h` (`EBodyPart { Boobs, Ass, Genitals }`) exist. `UClothingItem::CoveredBodyParts` uses the **old** enum; `UClothingItem::CanExpose` uses the **new** one. Half-migrated.
- **Mission system** — composable goals work but lacks the typed objective steps from §13.4 (`BeFullyNaked`, `BeFullyNakedNearNPCs`, `WalkNakedDistance`, `MoveDistanceFromClothing`, `BeObservedByNPCType`, `TakePhotoAtLocation`, `DeliverItemTo`). Missions still hand-authored in `MissionsConfig::DailyMissions` keyed by day index — no procedural generation, no Accept lifecycle (§13.1 / §13.2), no path-filtering on the generator (§13.4).
- **Day / night** — `UTimeOfDaySubsystem` now exposes `GetPhase()` / `IsDay()` and the `OnPhaseChanged` delegate (08:00 / 20:00 boundaries, §10.1). Not yet consumed: phase does not affect embarrassment gain, NPC type weighting, or police spawning. (`NPCSpawner` and its old `0921` window are deleted from the tree.)
### 1.3 Missing
- **Session loss — remaining (GDD §4.4)** — the transactional resolver exists (`USessionLossResolver`, see §1.1), but several outcomes depend on systems not yet built: the energy-zero trudge-home cutscene, the holding-cell cutscene, and the fade/teleport-to-apartment are delegated to BP via `OnSessionLossResolved` and not yet authored. The time skips now have a C++ entry point — `UTimeOfDaySubsystem::Sleep()` (sleep cycle) and `SkipToNextMorning()` (holding cell) — so the BP cutscenes call those rather than reimplementing the skip; this also runs the skip before the resolver's autosave, closing the prior ordering gap. Bag-placed-in-world loss is a TODO (bags absent, §6.4). `ClearWanted()` is a stub (no Wanted attribute until Phase 4/6, §7.7). Apartment-interior detection for loose-item loss is approximated (all `AItemPickup`s treated as "outside").
- **Three progression paths runtime (§5)** — enum exists; no XP pool, no per-path level derived from investment, no path-gated unlocks at runtime, no level-up flow. **XP is a single shared pool, not per-path** (GDD §5, §7.10).
- **Phone (§9)** — entire system absent: camera, gallery, livestream, bank, Feetex, maps, health tracker. Includes the new sub-systems:
- **Battery (§9.8)** — passive base + per-app multiplier drain; apartment charger; portable powerbank consumable (Convenience Store); hard shutdown at 0%; mid-livestream cutoff with earnings-to-date deposited; sleep always charges to 100%.
- **Livestream tip requests (§9.1.1)** — viewer-driven action requests with phone popup (Accept / Decline + countdown); fail = viewer count drops, no rep hit.
- **Livestream follower trickle (§9.1.1)** — `streamQualityScore` ticks through `FollowerGainCalculator` per tick.
- **Bank app income breakdown (§9.4)** — line items by source incl. the daily follower auto-deposit at the day-roll (§20 #25).
- **`PhoneSubsystem` (§17.1)** — tickable subsystem owning phone state (battery %, active app, livestream session lifecycle, charger interaction). Does not yet exist.
- **Forum (§13)** — no `UCommissionTemplate`, no procedural generation, no weekly missions distinct from daily, no profile (incl. weekly follower-income summary), no posting photos. Forum scope is locked minimal (board + own profile only, no threads / no other-users feed) — that's a non-build, but worth recording.
- **Photo & livestream** — absent.
- **NPC types (§10.2) — data model + Walker/Stalker landed (VS-5, partial).** `NPC/NPCType.h` defines the full `ENPCType` vocabulary (`Walker, Stalker, Blogger, Snitch, Harasser, Police`). `NPC/NPCTypeDefinition` (`UPrimaryDataAsset`, §17.4) carries `Type`, `ObservationWeight`, `bStopsToObserve`, `ObserveDurationSeconds`, `ReactionMontage`. `ANPC` holds a `NPCTypeDefinition` and exposes BlueprintPure `GetNPCType`/`GetObservationWeight`/`ShouldStopToObserve`/`GetObserveDuration` (Walker-ish fallbacks when null). The mechanical Walker/Stalker split is live: `UStatsManager::SetObserved` now takes a per-observer `Weight`, stored on the `FObserverEntry` and multiplied into `ComputeObservedExposureRate` so a Stalker's stare contributes more embarrassment than a Walker's glance; `ANPCAIController` reads the weight from the possessed `ANPC`'s type. **Still BP / later phases:** the BT branch on `ShouldStopToObserve` (Walker walks past vs Stalker stops & stares), the `DA_Walker`/`DA_Stalker` assets + BP variants, reaction montages, and the remaining types — Blogger needs Recognition (§7.6), Snitch/Police need wanted + chase (§10.3), Harasser needs the grope/evade beat. No Wanted-poster mechanic.
- **NPC crowd director (§17.1 `NPCManager`, §19) — landed.** `NPC/NPCDirectorSubsystem` (`UWorldSubsystem`, mirrors `UMissionSubsystem`) is the single authority for the live crowd around the player. **Mesh-agnostic** (NPCs will be lightweight skeletal meshes, **no MetaHumans / no motion matching** — decided 2026-06-01). On a light timer (`UpdateInterval`, default 0.5s) it reconciles population: recycles NPCs past `DespawnRadius`, trims to target when the phase target drops, and activates pooled NPCs at `UNavigationSystemV1::GetRandomReachablePointInRadius` points in the `[SpawnRadiusMin, SpawnRadiusMax]` ring (rejection-sampled outside the inner radius). Density target is day/night-driven via `UTimeOfDaySubsystem::OnPhaseChanged` (`TargetCountDay`/`TargetCountNight`). **Pooling:** `MaxNPCs` actors are prewarmed from `UNPCDirectorConfig::SpawnTable` (weighted `TSubclassOf<ANPC>` entries → composition) and recycled, never destroyed — no spawn hitch / GC churn. Config is `UNPCDirectorConfig` (`UPrimaryDataAsset`) on `UNakedDesireGameInstance::NPCDirector`. `ANPC` gained `ActivateFromPool`/`DeactivateToPool` (transform/visibility/collision/movement + `OnActivatedFromPool`/`OnDeactivatedToPool` BlueprintImplementableEvents) and crowd anim hygiene (`VisibilityBasedAnimTickOption = OnlyTickPoseWhenRendered` + URO). `ANPCAIController::ClearObservation` drops observation when an NPC is pooled out. **BP contract / follow-ups:** author `BP_NPC_*` classes (mesh + `UNPCTypeDefinition`) + a `DA_NPCDirector` config and assign it on the GI; the NPC BT must **start in `OnActivatedFromPool` and stop in `OnDeactivatedToPool`** (not on possess) so pooled NPCs don't tick AI; **remove `BPA_NPCSpawner` from the level** — the director replaces it. Liveliness behaviors (wander/destinations, idle-at-POI, reaction montages) remain BP, director-assisted. Background/instanced crowd layer deferred (handful density target).
- **Calendar, rent, sleep (§2.4, §15.2)** — **core landed (Phase 5):** `UTimeOfDaySubsystem` (`Global/TimeOfDaySubsystem`) owns the clock, rolls the calendar at 04:00, fires `OnHourChanged`/`OnDayChanged`/`OnPhaseChanged`, charges `WEEKLY_RENT` every 7th roll with immediate eviction via `OnCampaignEnded(Evicted)`, and exposes `Sleep()`/`SkipToNextMorning()`/`SkipTime()`. Mission refresh moved to `GameMode::HandleDayChanged`. The apartment **bed interactable** (`Interactables/Bed`) is in — interacting calls `USessionLossResolver::ResolveSleepLoss()` (outside-clothing loss) then `UTimeOfDaySubsystem::Sleep()`, with a BP `PlaySleepTransition` fade hook. **Still to do:** rewire the UltraDynamicSky actor to follow `SetCurrentTime` (and stop the old BP clock from advancing independently — otherwise the clock double-advances); the §4.4 cutscenes calling into the new skip API; the ending/eviction screen reacting to `OnCampaignEnded`; daily follower deposit is stubbed (no follower count until Phase 8); phone charge-to-100% on sleep stubbed (Phase 9). `WEEKLY_RENT` is a §21 tuning placeholder.
- **Item-world AActor (§6.1)** — no `AItemActor` / `AClothingPickup` base.
- **Bag inventory (§6.4)** — absent. Per the locked model (GDD §6.4 / §27): bags are the **only** container, capacity is a flat per-bag **item count** (no S/M/L size classes), and any item type can be stored. The container concept is removed from clothing — the per-garment `UClothingItemInstance::StoredItems` array has been deleted; the item list will live on a future bag `UItemInstance` subclass.
- **Dedicated carry slots (§6.5 / §27)** — `Phone`, `Bag`, `Key` are single-item slots on the player, separate from the 18 clothing/accessory/restraint/toy slots and from the old (removed) hand slot. Deliberately **not** in `EClothingSlotType` (it's clothing-named); each carry slot is modeled independently.
- **Phone slot + phone item — done (data + slot).** `Phone/PhoneItemDefinition` (`UItemDefinition`) + `Phone/PhoneItemInstance` (`UItemInstance`, per-instance `CurrentBattery` in `FPhoneInstanceState`; `MaxBattery` on the definition). The slot lives on `UInventorySubsystem`: `EquippedPhone`, `GetEquippedPhone()`, `OnPhoneChanged`, hot-swap on equip (§9.9). `EquipFromWardrobe`/`UnequipToWardrobe` route phones to `EquipPhone`/`UnequipPhone`; the equipped phone persists via the shared `EquippedItems` save bucket and re-hydrates in `EnsureHydrated` (clothing in that bucket is still hydrated by `ClothingManager`). **Follow-ups:** no phone-slot UI widget yet and the wardrobe list (`WardrobeInventoryWidget`) renders clothing only, so a phone is seedable via `StartingSaveData.Phone` / code but not yet equippable via the wardrobe screen; battery drain/charge is Phase 8/9.
- **Bag + key slots — pending** (deferred with the bag system, see above).
- **Theft (§6.3.4)** — new chance-based model (per-tick `P_theft` after grace period) not implemented. No theft timer / probability code at all.
- **Rip & tear (§6.3.5)** — absent. `UClothingItemInstance::Condition` is read-only in code; no decrement source.
- **Expose action (§6.3.6)** — `CanExpose` data exists on the definition; runtime action absent.
- **Restrictions system (§6.3.7 / §10.4.1)** — `IsRestrictive` boolean exists on `UClothingItem` but is now stale spec. The locked design is a **per-item `restrictions` list** with granular entries (`BlockRun`, `BlockCrouch`, `BlockPhoneUse`, `BlockItemPickup`, `BlockMasturbate`, `BlockExposeAction`, and parameterized `BlockSlotChange(slot)`). No code enforcement of any restriction flag; no Key-based unlock flow; no DBD-style unlock minigame (§10.4.1).
- **Bodysuit exclusion rule (§6.5)** — when implementing the new slot enum, `ClothingManager` must auto-unequip `Top`/`Bottom`/`UnderwearTop`/`UnderwearBottom` on bodysuit equip, and vice versa.
- **Toy slots (§6.5)** — `Nipples`, `Anal`, `Vagina` are now **toy slots** (not body-clothing slots, not body-parts conflict). Independent; all three can be active. Items in these slots are sex toys from the Adult Shop (§10.4), follow standard item-identity rules, do **not** contribute to coverage, and may modify lust / embarrassment / pulse plus add audible vibration NPCs may detect.
- **Adult shop, gym, beauty salon, café, convenience store** — none.
- **Food / consumables (§6.7)** — absent. Vocabulary now locked: 2 instant effects (energy restore, lust decrease), 4 timed buffs (stamina regen +, max stamina +, embarrassment-gain resistance, lust-gain resistance). Stacking: different types parallel; same type additive up to per-type cap. Cooking minigames (slice / stir / cook) modulate buff strength; never poison. No `UFoodItemDefinition` / `UFoodItemInstance` types yet.
- **Fridge — food home storage (§6.5 / §10.4 / §28)** — absent. Per the home-storage model, food is stored **only** in the apartment fridge (never the wardrobe). Needs: an `AFridge` interactable actor (mirror `AWardrobe`), a `FridgeItems` save bucket + `Add/Remove/Get` on `UGlobalSaveGameData`, fridge storage + `OnFridgeChanged` on `UInventorySubsystem`, the minimal food item types above, and a fridge screen widget. Tracks with the Phase 10 food system.
- **Recognition, wanted state, news (§7.6–§7.7, §11.1)** — absent. Face-cover bypass via `Face` and `Eyes` slots is part of this work.
- **Underwear selling (§15.1)** — absent. Backed by the `DeliverItemTo` typed objective (player-owned source only, §13.4).
- **Endless mode flag (§3.3)** — absent.
- **Pulse attribute (§7.5)** — absent (new in the updated GDD).
- **Hunger / max-energy decay (§7.3)** — absent. The new model has two layers of max energy: **base max** (gym progression) and **effective max** (decays via hunger). Eating any food restores effective max to base max as a universal built-in effect. Sleep does **not** clear hunger. `StatsManager` currently has only `MaxEnergy = 1000.0f` (`StatsManager.h:20`) — no hunger field, no decay tick, no eat-restores-hunger hook.
- **Police chase loss precedence (§4.4)** — when a chase is active and another loss fires, capture wins. No code logic for this yet (no chase exists, no precedence resolution in the loss path).
- **Holding-cell cutscene (§4.4)** — short non-interactive cutscene played when police capture and player can't pay. Time fast-forwards to next morning. Replaces the earlier "skip days proportional to debt" prose. Absent in code (no capture path exists yet).
- **Casino (§10.4.2)** — entire new location. Main floor (slots, blackjack, roulette), VIP room with per-day entrance fee, strip-game variants of blackjack and roulette that bet clothing items, lost-and-found inventory that retains `UItemInstance` identity for lost garments, mixed pacing (slots instant; table games burn in-game minutes), no poker. Significant scope: 3 minigames + VIP gating + strip-bet flow + lost-and-found UI.
- **Phone models + Electronics Shop (§9.9 / §10.4)** — three-tier phone (Starter / Mid / Pro) with three stat axes per tier (`camera quality`, `livestream quality`, `battery capacity`). New Electronics Shop location sells Mid and Pro. **Player can own multiple phones simultaneously**; only the equipped phone is active. Battery is per-phone-instance; hot-swap at the apartment wardrobe. Profile-side state (gallery, followers, history) does not move when swapping phones.
- **Restraint unlock minigame (§10.4.1)** — DBD-style skill checks. Successful hits speed up removal; misses have no penalty; no fail state. Restraint always comes off when the baseline timer expires. Absent (no restraint flow exists).
- **Instant commission rewards (§13.1 / §13.2 / §3.1 / §4.3)** — money / XP / followers credit at the moment of commission completion, not on return home. `UMissionsManager::CollectRewards` (`MissionsManager.cpp:35-51`) currently batches rewards into a manual "collect" call — this needs to be replaced with immediate crediting on `OnMissionCompleted`.
- **Hiding spots for ground items (§6.3.4)** — new bullet in the GDD that adds a hiding-spot concept; collides with the §4.4 "sleep = guaranteed loss" rule. Spec needs clarification before code (see Working notes).
- **GAS adoption (§17.2)** — `StatsManager` is still a bespoke `UActorComponent`; no `UAttributeSet`, no `GameplayEffect`-driven modifiers.
### 1.4 Conflicts to resolve
- **`SaveGame` UPROPERTY typo** — `GlobalSaveGameData.h:35` declares `UPROPERTY(SaceGame)` instead of `SaveGame` on `DaysPassed`. The field will not serialize.
- **`USaveSubsystem::SaveGame` writes an empty save** — does not pull state from the live world before writing (`SaveSubsystem.cpp:10-13`, `GlobalSaveGameData.cpp:45-53`). Any "save" call at present destroys progress. Highest-priority correctness bug.
- **`USaveSubsystem::LoadGame` ignores the loaded object** — static returns a populated `UGlobalSaveGameData`; subsystem stores / applies nothing.
- **`EClothingSlotType` — concrete reshape to the locked 18-slot list (GDD §6.5)**
Current enum (`Clothing/ClothingSlotType.h`, 14 values): `Nipples, Anal, Vagina, Head, Neck, Face, Eyes, Body, Top, Bottom, Bra, Panties, Socks, Shoes`.
Target (18 slots, 4 groups): see GDD §6.5. Migration table:
| Current | Target | Action |
|--------------|-------------------|---------------------------------------------------------------------|
| `Top` | `Top` | Keep |
| `Bottom` | `Bottom` | Keep |
| `Bra` | `UnderwearTop` | Rename |
| `Panties` | `UnderwearBottom` | Rename |
| `Body` | `Bodysuit` | Rename + enforce exclusion with `Top`/`Bottom`/`UnderwearTop`/`UnderwearBottom` |
| `Socks` | `Socks` | Keep |
| `Shoes` | `Footwear` | Rename |
| — | `Outerwear` | **Add** |
| `Head` | `Head` | Keep |
| `Face` | `Face` | Keep (now a face-cover slot per §7.6) |
| `Eyes` | `Eyes` | Keep (now a face-cover slot per §7.6) |
| `Neck` | `Neck` | Keep |
| — | `WristRestraint` | **Add** |
| — | `AnkleRestraint` | **Add** |
| — | `NeckRestraint` | **Add** (distinct from cosmetic `Neck`; auto-exclusion is *not* enforced — content authoring handles visual collision) |
| `Nipples` | `Nipples` | Keep — **now a toy slot, not a body-clothing slot** |
| `Anal` | `Anal` | Keep — **now a toy slot** |
| `Vagina` | `Vagina` | Keep — **now a toy slot** |
Net: 14 → 18 (4 renames, 4 additions, 3 semantic repurposes). `NakedDesireCharacter::SetupClothingSlots` (`NakedDesireCharacter.cpp:357-433`) needs four new mesh components added and four existing components renamed in lockstep with BP references in `Content/Blueprints/Player`.
Bodysuit exclusion is a runtime rule on `ClothingManager::PutOnClothing` — equipping a bodysuit must un-equip the four body slots; equipping any of those four must un-equip a present bodysuit.
Toy slots are independent and stackable; their items are sex toys (not clothing) but use the same `UItemInstance` model. The `UClothingItem` data class is named for clothing — a `USexToyItem` (or similar) `UItemInstance` subclass may be cleaner than reusing `UClothingItem` for toys, since the toy effects (lust modifier, embarrassment modifier, pulse modifier, NPC-audible vibration) don't map onto clothing's coverage / `CanExpose` / `IsRestrictive` model.
- **Two competing body-part enums** — `EPrivateBodyPartType` vs. `EBodyPart`. `ClothingItem::CoveredBodyParts` uses one, `ClothingItem::CanExpose` uses the other. The censorship / observation code paths in `NakedDesireCharacter` and `ClothingManager` are still on `EPrivateBodyPartType`. Pick one (recommend `EBodyPart`) and migrate.
- **`UClothingList`** forward-declared in `GlobalSaveGameData.h:11`; `Wardrobe` and save records no longer use it. Dead reference; remove.
- **`Money` representation** — `ANakedDesireCharacter::Money` is `int` (`NakedDesireCharacter.h:132`); `UGlobalSaveGameData::Money` is `float` (`GlobalSaveGameData.h:27`). Pick one authoritative location and one type.
- **`STARTING_MONEY`** macro defined in `Constants.h:8` but unreferenced. Either wire it into save / character init or remove.
- **`ClothingItemInstance` has no public constructor / factory** — `ClothingItem` pointer is `protected` with no `Init(UClothingItem*)`. Wardrobe / BuyItem flows rely on `EditAnywhere Instanced` design-time population, which won't support runtime purchase or save-driven hydration.
- **`UClothingItem::IsUnderwear` is dead spec** — `ClothingItem.h:82`. The locked design (§20 #20) drops the field entirely; `coverage` is authored directly to reflect the "just underwear" discount. Remove the field and any references during Phase 1.
- **`UClothingItem::IsRestrictive` is dead spec** — `ClothingItem.h:76`. The locked design (§20 #21) replaces the boolean with a granular `restrictions` list. Remove the boolean and add a `TArray<FClothingRestriction>` (or equivalent) supporting `BlockRun`, `BlockCrouch`, `BlockPhoneUse`, `BlockItemPickup`, `BlockMasturbate`, `BlockExposeAction`, and `BlockSlotChange(slot)`. Restriction evaluation is the union across all equipped items.
- **`UClothingItem` per-body-part coverage TODO is now resolved against** — `ClothingItem.h:61` carries `// TODO: Add coverage per body part`. The locked spec uses a single `coverage` value per item (multi body parts via `covers` set + `max()` formula, §6.3.2). The TODO comment should be removed; per-body-part coverage is intentionally not the model.
- **`StatsManager::TickComponent` energy drain** — drains energy at a flat `0.9f` per tick (`StatsManager.cpp:26`) regardless of activity. GDD §7.3 specifies per-activity modifiers.
- **`NPCSpawner` day window** — uses `Hours >= 9 && < 21` for "day" (`NPCSpawner.cpp:40`); GDD §10.1 says `08:0020:00`. Off by one hour on each end.
- **GDD shifts not yet reflected in code:**
- **Loss resolver** must not strip equipped clothing (§4.4).
- **Energy = 0** must trigger a "cutscene → apartment → sleep" flow, not a teleport (§4.4).
- **Embarrassment = max** must fade to apartment with **no extra cost** (no time skip, no money penalty, no rep hit) — GDD §4.4.
- **Police chase precedence (§4.4):** if a chase is active when any other loss fires, the loss resolves as police capture — chase wins.
- **Police capture, can't pay (§4.4):** short non-interactive holding-cell cutscene + time skip to next morning. Replaces the earlier "skip days proportional to debt" model.
- **Theft** is a chance-based model with grace periods and flat per-tick `P_theft`, not a ramping curve (§6.3.4).
- **Sleep** is the deterministic side of clothing loss — guaranteed loss of every clothing item left outside the apartment (§4.4). Hiding-spots bullet in §6.3.4 currently contradicts this — see Working notes.
- **No Helper NPC**; restraint removal is **Key + timed-unlock action with a DBD-style skill-check minigame** (§10.4.1). Successful checks speed up removal; missed checks have no penalty; restraint always comes off when the baseline timer expires.
- **Per-item `restrictions` list** replaces `IsRestrictive` boolean (§6.3.7). Granular vocabulary: `BlockRun`, `BlockCrouch`, `BlockPhoneUse`, `BlockItemPickup`, `BlockMasturbate`, `BlockExposeAction`, parameterized `BlockSlotChange(slot)`. Restrictions union across equipped items.
- **Coverage math** is `max()` across garments covering a body part — not sum. No underwear halving; the `IsUnderwear` flag is removed entirely (§6.3.2).
- **Hunger / max-energy decay (§7.3):** two-layer max energy — base (gym) and effective (decays via hunger, restored by eating). Eating any food triggers the restoration as a universal built-in effect. Sleep does not clear hunger.
- **Masturbation gating is home-vs-public** (§23 #25, resolved): home masturbation is always available to every player; *in-session* (public) masturbation is Slut-path-gated. Generator filters `PerformAction(masturbate)` (a public objective) for non-Slut players; the §14.1 quick-action shows the entry unconditionally at home and hides the in-session entry until the player has Slut investment (§5.1 / §7.2 / §14.1).
- **Police** spawn day **and** night while `wanted` (§7.7 / §10.3).
- **XP is a single shared pool, not per-path** — Phase 9 implementation must use a shared XP counter; a path's level is derived from how many upgrades the player has bought from that path's attribute pool (§5, §7.10).
- **Commission lifecycle:** explicit Accept required for daily *and* weekly; un-accepted commissions carry no penalty; weekly rewards now include followers (§13.1 / §13.2). **Rewards land instantly on completion** — money / XP / followers credit at the moment the commission completes; no return-to-home "collect rewards" step. Replace the manual `UMissionsManager::CollectRewards` flow with immediate crediting on `OnMissionCompleted`.
- **Commission generator** must respect `pathRequirement` (incl. `None` = path-neutral) and filter templates whose steps reference path-locked actions (§13.4). `failurePenalty` field is canonical; generator supplies tier-scaled default when empty.
- **`DeliverItemTo`** must source from player inventory only; commission does not auto-issue items. Worn-underwear sales (§15.1) are the canonical use case (§13.4).
- **Livestream → follower trickle** via `FollowerGainCalculator` per tick; signed by reputation. Tip requests give an additional bonus on completion (§9.1.1 / §13.5).
- **Phone models (§9.9):** three tiers with three stat axes each (camera quality, livestream quality, battery capacity). Bought at the new Electronics Shop. Player may own multiple phones; gallery / followers / stats live on the profile (not the phone); battery is per-phone-instance.
- **Casino (§10.4 / §10.4.2):** main floor (slots / blackjack / roulette, money only), VIP room (per-day entrance fee), strip-game blackjack and roulette tables in VIP that bet clothing. Lost garments go to a casino lost-and-found inventory that retains `UItemInstance` identity. Mixed pacing.
- **Locations locked for launch:** Beach + Train Station + Casino + Electronics Shop are in; School exterior and Hot springs were cut. Vertical slice is the basic shop set only (§10.4, §18.1).
- **Food vocabulary locked:** 2 instant effects (energy restore, lust decrease) + 4 timed buffs (stamina regen +, max stamina +, embarrassment-gain resistance, lust-gain resistance); same-type stacks additively up to a cap; never poison (§6.7). Plus the universal "eating restores effective max energy" hunger reset.
---
## 2. Architectural premise
The item-identity rule (§6.1) and the save contract (§16.3) are still load-bearing. With the definition / instance split in place, the next bottleneck is **wiring serialization through gameplay**: condition mutation, world drops, container parent links, hydration on load. Until that round-trip works, content on top of the current system has no durable home.
The session-system + loss resolver (Phase 3) is the second pin. Several GDD shifts (equipped-never-dropped, energy-zero cutscene, embarrassment-max no-extras, sleep-deterministic clothing loss) move complexity from §4.4 prose into a single deterministic resolver. Build it once, well.
GAS adoption (§17.2) remains deferred until Phase 4. Adding new attributes to the bespoke `StatsManager` in the meantime is fine — keep them additive and easy to migrate. Food buffs (§6.7) are external multipliers on attribute rates, so design the per-rate interface with that in mind even before GAS lands.
---
## 3. Phased roadmap
Phase estimates are rough and assume one engineer. Adjust as we go.
### Phase 1 — Finish item identity + save round-trip; reshape slot enum (12 weeks)
- Fix the `SaceGame` UPROPERTY typo on `DaysPassed` (`GlobalSaveGameData.h:35`).
- Add `UClothingItemInstance::Init(UClothingItem* Definition)` (or static factory) so instances can be constructed at runtime.
- Make `USaveSubsystem::SaveGame` actually capture live state into `UGlobalSaveGameData`: player money, equipped items, wardrobe items, calendar, observation-attribute snapshot. Populate `FItemSaveRecord` from current instances.
- Make `USaveSubsystem::LoadGame` actually apply the loaded data: rebuild instances from records, push into wardrobe + `ClothingManager` slots. Replace the commented-out `ClothingManager::HydrateClothing` with real hydration.
- Unify `EPrivateBodyPartType` and `EBodyPart` on `EBodyPart`. Migrate `ClothingItem::CoveredBodyParts`, `ClothingManager::IsBodyTypeExposed`, censorship paths in `NakedDesireCharacter`, BT / observation paths.
- **Reshape `EClothingSlotType` to the locked 18-slot list (§1.4 table).** Add `Outerwear`, `WristRestraint`, `AnkleRestraint`, `NeckRestraint`. Rename `Bra → UnderwearTop`, `Panties → UnderwearBottom`, `Body → Bodysuit`, `Shoes → Footwear`. Add four new `USkeletalMeshComponent`s in `NakedDesireCharacter` for the new slots. Update BP references in lockstep.
- **Implement Bodysuit exclusion** in `ClothingManager::PutOnClothing` (auto-unequip `Top`/`Bottom`/`UnderwearTop`/`UnderwearBottom`; vice versa).
- **Define toy items**: either a new `USexToyItem : UItemInstance` (recommended) or extend `UClothingItem` with a toy variant. Toy items target `Nipples` / `Anal` / `Vagina` slots, carry lust / embarrassment / pulse modifier fields, and an optional NPC-audible-vibration flag. Do not contribute to coverage.
- **Drop dead spec from `UClothingItem`**: remove `IsUnderwear` (`ClothingItem.h:82`) and `IsRestrictive` (`ClothingItem.h:76`); remove the `// TODO: Add coverage per body part` comment on line 61 (resolved against — single `coverage` is locked spec).
- **Replace `IsRestrictive` with a `restrictions` list** on `UClothingItem`: `TArray<FClothingRestriction>` (struct with `ERestrictionType` enum + optional `EClothingSlotType` parameter for `BlockSlotChange`). No runtime enforcement yet (Phase 4 / 10 own that) — Phase 1 just needs the data field, the struct, and the enum.
- Money: pick one authoritative location (`UGlobalSaveGameData`), make `ANakedDesireCharacter::Money` a read-through. Apply `STARTING_MONEY` at new-save creation. Reconcile int vs. float.
- Remove dead `UClothingList` forward declarations.
**Exit criteria:**
1. Item dropped at runtime, condition modified, save → quit → load reproduces the same `FGuid`, condition value, and slot / wardrobe membership.
2. `EBodyPart` is the only body-part enum in use.
3. `EClothingSlotType` matches the §6.5 18-slot list. Bodysuit exclusion is enforced at runtime. Toy slots work with at least one placeholder toy item.
4. A new game starts with `STARTING_MONEY` and rent / purchase flows debit the canonical money field.
### Phase 2 — World item actors + drop / pickup (1 week)
- `AItemActor` base wrapping a `UItemInstance*` (in `Items/`). Spawn transform, owning instance pointer, SaveGame-tagged.
- `AClothingPickup : AItemActor` with a visual representation (`UClothingItem::StaticMesh` when set, fallback to skeletal mesh impostor).
- Hook `ClothingManager::DropClothing` to spawn `AClothingPickup` at the player location with the actual instance reference — *not* a template copy. Reparent the instance from "equipped slot" to "world".
- Pickup interaction via existing `UInteractionManager` + `IInteractionTarget`. Pickup re-parents the instance into wardrobe or directly into the matching slot.
- `USaveSubsystem` extension: serialize `AClothingPickup`s as `FItemSaveRecord`s with world transform.
**Exit criteria:** unequip → world pickup actor appears → save / reload → pickup still there at the same transform with the same instance ID → pick up restores the same instance to the slot.
### Phase 3 — Session system + loss resolver (1 week)
- ~~`USessionManager` (subsystem on `UNakedDesireGameInstance` or a `UWorldSubsystem`). Apartment `ALocationTrigger` flag drives session start / end. Emits `OnSessionStart` / `OnSessionEnd` with cause.~~ **DONE (DEV-99)**`USessionManagerSubsystem` (`UWorldSubsystem`, `Global/`). See §1.1.
- ~~`USessionLossResolver` — single class, one method `ResolveLoss(ESessionLossCause Cause)`.~~ **DONE (DEV-100)**`Global/SessionLossResolver` (`UWorldSubsystem`). See §1.1. C++ owns the transaction (precedence, money, item loss, autosave); cutscenes / fade / time-skip are delegated to BP via `OnSessionLossResolved`. Implements GDD §4.4:
- **Police chase precedence**: if a chase is active (between detection and the disengage timer, §10.3), force `Cause = PoliceCapture` regardless of what triggered the loss. Chase always wins.
- Equipped clothing **stays** equipped (do not strip).
- Bag placed in world: mark its world record for deletion.
- Loose clothing on ground: leave as-is at this stage; sleep step finalizes any guaranteed loss.
- **Energy = 0**: trigger a cutscene → apartment → sleep cycle → guaranteed loss of every world clothing outside the apartment.
- **Embarrassment = max**: fade to apartment. **No extra cost** (no time skip, no money, no rep).
- **Police capture (can pay)**: fade to apartment, deduct money penalty, clear `wanted`.
- **Police capture (can't pay)**: short non-interactive holding-cell cutscene; time fast-forwards to next morning; debt settled; clear `wanted`. Already-accepted commissions still expire at day end with their normal `failurePenalty` — the cutscene does not pause the day clock.
- ~~Wire `StatsManager::IncreaseEmbarrassment` max-hit and energy-zero into~~ the session subsystem — **DONE (DEV-99)**: `USessionManagerSubsystem` subscribes to `EmbarrassmentUpdate` / `EnergyUpdate` and the C++ embarrassment path no longer calls `EndGameEmbarrassed`. DEV-100 routes `OnSessionEnd` into `USessionLossResolver`.
- Add a debug overlay showing the current loss state and what would be lost if a given cause fired.
**Exit criteria:** all four code paths (safe end, embarrassment max, energy zero, police capture) produce the §4.4 outcomes deterministically. Inventory state after each loss matches the §6.6 summary table 1:1.
### Phase 4 — Attributes refactor (12 weeks)
- Decision: GAS vs. extended `StatsManager`. Recommend GAS — the embarrassment formula has Pulse, Recognition, Coverage, Day/Night, per-NPC-type weights, and food-buff multipliers (§7.1, §6.7), which is exactly what `UGameplayEffect` is shaped for.
- Add Lust (§7.2), **Pulse (§7.5)**, Recognition (§7.6), Wanted (§7.7), Reputation (§7.8), Followers (§7.9). Money remains in the save schema directly (non-attribute side effects).
- **Hunger / max-energy decay (§7.3):** add a second layer to max energy. `baseMaxEnergy` (gym progression) stays; introduce `effectiveMaxEnergy` (decays at `maxEnergyDecayRate` per in-game hour toward a floor). Eating any food sets `effectiveMaxEnergy` back to `baseMaxEnergy` as a universal hook in the eat-item flow. Sleep does **not** reset hunger. Health Tracker (§9.7) surfaces effective max alongside current energy.
- Implement `GetEffectiveCoverage(EBodyPart)` on `ClothingManager` per §6.3.2 — **`max()` across garments covering the part** (no sum, no underwear halving). Replace the `0.0f` stub in `StatsManager::TickComponent`.
- Lust → masturbate action (§23 #25): home masturbation always available; in-session (public) masturbation Slut-path-gated. Block only the in-session quick-action entry when the player has no Slut investment; the home entry is always live (§7.2 / §14.1).
- **Pulse simulation:** rises with running, masturbation, exposure events; decays toward baseline at rest. Modifies embarrassment and lust gain rates (§7.5).
- Recognition rise from Blogger photo events; **face-cover bypass via `Face` + `Eyes` slots** (§7.6); reduction via news-site reporting (§11.1).
- Define the per-rate multiplier interface that food buffs (§6.7) and toy effects (§6.5) will hook into. Don't ship the buffs here; just leave the seams.
- Debug overlay extension: live values + active modifier sources for every attribute.
**Exit criteria:**
1. Observed-while-exposed embarrassment gain reads coverage and modifiers from the attribute system (no more `0.0f` stub).
2. Lust + Pulse simulate correctly and visibly influence embarrassment in a playtest scenario.
3. Recognition increases after a (mocked) photo event and decreases after a news-report action. Face-cover slot occupancy reduces recognition gain.
### Phase 5 — Time + calendar + rent + sleep (34 days)
- `UTimeOfDaySubsystem` (`UTickableWorldSubsystem`, consistent with `SessionManagerSubsystem` / `SessionLossResolver`) **owns the authoritative clock in C++** and pushes time to the UltraDynamicSky actor each tick (decision 2026-05-30: invert the old BP→C++ flow; the UDS actor's time input is rewired to follow `SetCurrentTime`). Replaces the BP-implementable time on `NakedDesireGameMode`. 90-day calendar, week boundary, day phase `08:0020:00`. (The old `NPCSpawner.cpp:40` `0921` window is moot — `NPCSpawner` is deleted in the working tree.)
- Clock state: `CurrentDay` (= `DaysPassed`) + `MinuteOfDay` (replaces the loose `HourOfDay` float). Rate constant `INGAME_MINUTES_PER_REAL_SECOND = 16` (1440 min / 90 real-min cycle). Delegates: keep `OnHourChanged(int32)` (sky + BP gates), add `OnDayChanged(int32)` and `OnPhaseChanged(EDayPhase)`. **Calendar rolls at the fixed 04:00 boundary** (decision 2026-05-30; matches the old `Hour==4` code); daily-mission refresh moves out of `GameMode::OnHourChanged(Hour==4)` into `OnDayChanged`.
- Sleep action on apartment bed: triggers the same path as energy-zero (§4.4) for the "items left outside" cleanup, restores energy (clamped to current effective max), autosaves, advances calendar via `SkipTime(8h)`. **Charges the equipped phone to 100%** (§9.8). **Does NOT reset hunger** (§7.3) — only eating clears effective-max decay. The §4.4 cutscenes call `Sleep()` / `SkipToNextMorning()`, which also closes the resolver's autosave-ordering gap (time skip now runs in C++ before the autosave).
- Weekly rent transaction every 7th day-roll; **immediate eviction → game over (run)** if money insufficient (§3.3, §22 #8) — no grace period (decision 2026-05-30).
- **Daily follower-income auto-deposit** to the bank at each day-roll (§7.9, §9.4; reconciled to daily — README §20 #25). ⚠️ Depends on a follower-count attribute that doesn't exist until Phase 8 — wire the `OnDayChanged` hook + rate constant now; payout reads 0 until followers land.
- Endless-mode flag (`bEndlessMode`) on `UGlobalSaveGameData`; the rent-charge branch is skipped entirely when set.
- Eviction needs an end-campaign entry point — add an `OnCampaignEnded(EEndReason)` delegate rather than overloading the legacy `EndGameEmbarrassed` BIE (consistent with the session-system refactor's direction).
**Exit criteria:** play through 7 in-game days, get charged rent at the week boundary, eviction triggers on insufficient funds, endless-mode disables eviction, daily follower income lands in the bank, and the day/phase/calendar survives a save → quit → reload.
### Phase 6 — NPC types + recognition pipeline (12 weeks)
- `ENPCType` enum, type-specific BT branches or per-type controller subclasses: Walker / Stalker / Blogger / Snitch / Harasser. **No Helper.**
- Police as a separate AI controller / spawn pipeline. Patrols spawn day **and** night while `wanted` (§7.7 / §10.3). Detection logic (face hidden? + coverage threshold) → chase → break-LOS timer.
- Wanted-poster mechanic: spawn posters in the city; tearing all down clears `wanted` (§10.3).
- Recognition flow: Blogger sight + photo event → article → news-site article state → "report" reduces recognition.
- Wire Snitch's "report to police" → immediate police spawn this session + sets `wanted` (§7.7).
**Exit criteria:** every listed NPC type observably distinct; recognition rises after a Blogger event and can be reduced via news report; Snitch report triggers police spawn and the `wanted` tag that persists across sessions until posters torn down or capture.
### Phase 7 — Commission template system + accept lifecycle (12 weeks)
- Replace `MissionsConfig`'s hand-authored daily list with `UCommissionTemplate` + procedural generation per §13.4.
- Add typed objective steps as new `UMissionGoal` subclasses:
- `BeFullyNaked(duration)`
- `BeFullyNakedNearNPCs(count, duration)`
- `WalkNakedDistance(meters)`
- `MoveDistanceFromClothing(meters)`
- `BeObservedByNPCType(type, durationOrCount)`
- `TakePhotoAtLocation(locationTag)` (gated by Phase 8 phone work)
- `DeliverItemTo(npcOrLocation)`**player-owned inventory only** (§13.4); the commission does not auto-issue items.
- `PerformAction(actionId)`**generator filters path-locked actions** (e.g., `masturbate` is Slut-only).
- **Accept lifecycle (§13.1 / §13.2):**
- Both daily and weekly require explicit Accept on the forum.
- Un-accepted commissions carry no penalty; accepted-but-failed apply the template's `failurePenalty`.
- Weekly rewards include followers; weekly `failurePenalty` is heavier than daily.
- **Instant rewards on completion (§13.1 / §13.2 / §3.1 / §4.3):** money wires to the bank, XP credits to the shared pool, followers update on the profile **at the moment of completion** — not on return home. Replace the current `UMissionsManager::CollectRewards` batched flow with immediate crediting in `OnMissionCompleted`. There is no "collect rewards" step at the apartment.
- **Generator rules:** respect `pathRequirement` (incl. `None`); filter path-locked-action templates by player path investment; substitute a tier-scaled default penalty when `failurePenalty` is empty.
**Exit criteria:** daily commissions regenerate from templates each in-game day; weekly arc is distinct; explicit Accept committed flow works end-to-end; rewards credit instantly on completion (no return-home gate); failed accepts deduct the template penalty; un-accepted commissions never penalize the player.
### Phase 8 — Phone + forum UI + battery + livestream (34 weeks)
- **Phone UI shell — landed early (pulled forward from this phase).** CommonUI nested-stack shell under `UI/Phone/`: `UPhoneScreenWidget` (activatable pushed onto the GameLayout `WidgetStack`) owns an inner `AppStack` (`UCommonActivatableWidgetStack`) with the home screen at its base; `OpenApp` pushes an app, the physical `HomeButton`/`GoHome` clears back to home, and CommonUI back navigation pops app→home→close-phone for free. `UPhoneAppWidget` is the abstract base for every app; `UPhoneHomeScreenWidget` (a `PhoneAppWidget`) builds an icon grid from a data-driven `AppEntries` array (`FPhoneAppEntry` = name/icon/`TSubclassOf<UPhoneAppWidget>`, §17.4) into a `BindWidget` `AppContainer`, spawning one `UPhoneAppIconWidget` each and routing taps back to `OpenApp` via `OnAppSelected`. `UGameLayoutWidget::OpenPhone()` pushes the shell. **Open path is temporary:** a dev `PhoneAction` input on `ANakedDesireCharacter` (`OnPhonePress`) calls `OpenPhone()` — replace with phone-slot interaction + `BlockPhoneUse`/battery gating when the systems below land. **BP to author in-editor:** `WBP_PhoneScreen` (binds `AppStack`, `HomeButton`; set `HomeScreenClass`), `WBP_PhoneHome` (binds `AppContainer`; set `AppIconWidgetClass` + fill `AppEntries`), `WBP_PhoneAppIcon` (binds `IconButton`; optional `IconImage`/`NameText`), placeholder `WBP_App_Camera`/`Gallery`/`Forum` (each a `UPhoneAppWidget`); assign `PhoneScreenWidgetClass` on the GameLayout BP and the `PhoneAction` `UInputAction` + mapping on the player BP. App **contents** (capture, posting, etc.) still pending below.
- **Forum app — commissions tab landed.** `UI/Phone/Apps/`: `UForumAppWidget` (a `UPhoneAppWidget`) hosts two bottom tabs as `BindWidget` `UWidgetSwitcher` pages (Commissions index 0 / Profile index 1) toggled by `CommissionsTabButton`/`ProfileTabButton`; opens on Commissions. `UForumCommissionsWidget` (the Commissions page) reads the live board from `UMissionSubsystem`, splits offered commissions by `GetTier()` into `DailyContainer`/`WeeklyContainer`, fills `AcceptedContainer` (both tiers) + `CompletedContainer` (completed-this-period), rebuilds on `OnBoardChanged`, and routes row accept/abandon to `AcceptCommission`/`AbandonCommission`. `UForumCommissionWidget` is one row — title/poster/reward/objective-progress, with state-gated Accept (Offered) / Abandon (Accepted) buttons reported up via raw delegates. Added `UMissionSubsystem::GetCompletedCommissions()`. **BP to author:** `WBP_App_Forum` reparented to `UForumAppWidget` (bind `TabSwitcher` + two tab buttons; switcher child 0 = commissions widget, child 1 = profile placeholder), `WBP_ForumCommissions` reparented to `UForumCommissionsWidget` (bind the four scroll/box containers; set `CommissionEntryClass`), `WBP_ForumCommissionEntry` reparented to `UForumCommissionWidget` (bind `TitleText`/`DescriptionText`/`AcceptButton`; optional `PosterText`/`RewardText`/`AbandonButton`). **Pending:** the Profile tab page (followers / gallery / livestream history) is Phase 8 content.
- Phone as an `AItemActor` (Phase 2 base). Usable from the dedicated **phone slot** (§6.5 / §27); placed-in-world streaming exception still applies (§9.1.1).
- `PhoneSubsystem` (§17.1): tickable; owns battery %, active app, livestream session lifecycle, charger interaction.
- **Battery (§9.8):**
- Passive base drain + per-app multiplier (Forum/Bank/Maps/Health Tracker/Gallery ~1×, back-camera ~3×, livestream ~5×). Livestream stacks on top of foreground app.
- Apartment charger interactable: place phone on it, recharges at a fixed rate.
- **Portable powerbank**: consumable `UItemInstance` purchasable at Convenience Store (§10.4). Single-use, restores fixed % when consumed. Stored in the equipped bag like any other item (no size classes, §6.4 / §27).
- **Hard shutdown at 0%**: all apps lock; livestream ends and deposits earnings-to-date; future tips forfeit. Phone wakes from a consumed powerbank.
- **Sleep auto-charges to 100%** (voluntary or energy-zero cutscene).
- Battery % is part of the phone's `UItemInstance` state; persists across saves.
- Apps: Camera + Gallery, Livestream, Bank, Feetex, Maps, Health Tracker, Forum.
- **Bank app income breakdown (§9.4):** line items by source — commissions, livestream donations, in-stream tip-request completions, underwear sales, weekly follower auto-deposit.
- **Livestream (§9.1.1):**
- `UStreamSession` tickable UObject — works while phone is held OR placed.
- Per-tick `streamQualityScore``FollowerGainCalculator` for the follower trickle (signed by reputation; shrinks at negative rep).
- End-of-stream deposits both money and accumulated follower delta.
- **Tip requests:** phone popup with Accept / Decline + countdown. Accept = spawn live objective tracker (reuse Phase 7 primitives). Complete = tip lands + small follower bonus. Fail = viewer count drops (less passive income for the remainder); no rep hit. Decline / timeout = no effect.
- Photo system: render-to-texture (§17.6 / §21 Q5 — recommended).
- Wire `TakePhotoAtLocation` commission step to actual photo events.
- **Phone models + Electronics Shop (§9.9 / §10.4):**
- 3 tiers (Starter / Mid / Pro). Each is a `UClothingItem`-style data asset (or dedicated `UPhoneItem` definition) with `CameraQuality`, `LivestreamQuality`, `BatteryCapacity` multipliers.
- Starter is owned at game start. Mid and Pro are bought in-person at the new **Electronics Shop** location (interactable like Wardrobe / Adult Shop).
- Player can own multiple phones simultaneously — store them in the wardrobe / inventory as regular `UItemInstance`s. Only the equipped phone is active.
- Hot-swap from the apartment wardrobe; battery percentage is per-phone-instance and **does not transfer** on swap.
- Profile state (gallery, follower count, livestream history) lives on the player profile, never on the phone — swapping phones does not move or duplicate this state.
- `CameraQuality` multiplies `exposureScore` for photo posts (§13.5). `LivestreamQuality` multiplies per-tick `streamQualityScore`. `BatteryCapacity` multiplies the total charge budget (drain rates unchanged).
**Exit criteria:** end-to-end photo loop (capture → gallery → post → follower count updates). Livestream can be started, run while the phone is placed, generate trickle followers + donations, accept and complete a tip request for a bonus, end with payout. Battery drain visibly different per app; charger and powerbank both restore charge; 0% hard-shutdown behaves correctly. Buying a Mid / Pro phone at the Electronics Shop adds it to the wardrobe; equipping it visibly improves photo / stream gain rates per the multipliers.
### Phase 9 — Path progression + content authoring (2 weeks)
- **Single shared XP pool** — not per-path. Add `XP` to the save schema. Player invests XP via the forum profile (§13.3) into any path's attribute pool.
- A path's level is derived from how many upgrades the player has purchased from that path's pool (§5.4 / §7.10) — there is no per-path XP counter.
- Level-gating on clothing (§5.15.3) and on path-tagged commission templates (§7).
- Author the vertical-slice content set from GDD §18.1 (1520 clothing items, 3 bag variants, 8 food items, 20 commission templates, 1 functional district).
- Masturbation gating (§5.1, §7.2, §23 #25) — hide only the *in-session* quick-action entry until the player has at least one Slut-path upgrade; the home (apartment) entry is always available.
**Exit criteria:** all three paths have at least 5 unlockable items and 3 path-specific commission templates. Slut-path-locked actions correctly hidden until the player invests. XP spent into one path raises *that* path's level without consuming XP that could go to another path.
### Phase 10 — Remaining systems & polish
- Bag inventory (§6.4) — uses Phase 2's `AItemActor` for world placement. Bag `UItemInstance` subclass holds a count-limited list of any `UItemInstance` (no size classes); add the `Phone` / `Bag` / `Key` carry slots (§6.5 / §27).
- **Food (§6.7):**
- Implement the locked vocabulary: 2 instant effects (energy restore, lust decrease) + 4 timed buffs (stamina regen +, max stamina +, embarrassment-gain resistance, lust-gain resistance).
- Universal built-in effect: every food restores effective max energy to base max (hunger reset; §7.3 hookpoint from Phase 4).
- Stacking: different types parallel; same type additive to a per-type cap.
- Three cooking minigames (slice / stir / cook); scoring modulates buff strength; never poison.
- Gym / beauty salon / adult shop / café / convenience store (§10.4). Convenience Store stocks powerbanks (Phase 8 dep). Adult Shop stocks toy items for the three toy slots (Phase 1 dep).
- Rip & tear (§6.3.5).
- **Theft model (§6.3.4)** — implement `T_grace` / `T_grace_bag` grace timers and per-tick `P_theft` chance roll. No ramp. Hiding-spot mechanic is currently spec-ambiguous (see Working notes) — coordinate before implementing.
- Expose action (§6.3.6) — read `CanExpose` per garment, blocked by overlapping coverage. Also blocked when `BlockExposeAction` is active in the restrictions set.
- **Restrictions enforcement (§6.3.7 / §10.4.1):**
- Wire each `Block*` flag into the relevant subsystem (`BlockRun` into movement, `BlockPhoneUse` into `PhoneSubsystem`, `BlockMasturbate` into the quick action, `BlockSlotChange(slot)` into `ClothingManager::PutOnClothing` / `RemoveClothing`, etc.).
- **Restraint removal:** Key + timed-unlock action with a **DBD-style skill-check minigame** (§10.4.1). Rotating pointer + target zone; hits speed up removal; misses do nothing; no fail state. Restraint always comes off when the baseline timer expires.
- **Casino (§10.4 / §10.4.2):** new location, significant scope.
- Three game UIs: slots (instant single-button), blackjack (vs. dealer NPC, in-game-minute pacing), roulette (multi-bet single spin, in-game-minute pacing). No poker.
- VIP room interactable: pay per-day entrance fee at reception; access flag clears at next day boundary.
- Strip-game blackjack and roulette tables in VIP: bet a clothing item from an equipped slot. Lost garments transfer to a **casino lost-and-found** safe inventory keyed to the player; retains `UItemInstance` identity per §6.1. Reclaim by winning back at the same table or paying a flat buy-back fee at reception.
- Wire casino income/loss into the Bank app income breakdown (§9.4).
- Underwear selling (§15.1) — Feetex drop-box or location drop-off variant. Backed by `DeliverItemTo(npcOrLocation)` from Phase 7.
- Wanted-poster takedown (§10.3) — interaction + spawn placement pass.
- Beach + Train Station locations (§10.4) for full-launch scope.
- Final tuning of all §21 numbers (theft, food caps, battery, tip-request distribution, embarrassment / lust / energy / pulse rates, face-cover magnitudes, casino RTP + VIP fee, phone-tier multipliers, restraint minigame parameters, hunger decay, etc.).
---
## 4. Working notes
Use this section for in-flight decisions, blockers, and open questions that emerge during a phase. Move resolved items into the README's §20 "Resolved Design Decisions" or into git history.
- **Phase 1 critical path:** the save-write-without-state bug is the most damaging current issue — any "save" call destroys progress. Fix the SaveSubsystem before anything else builds on save behaviour.
- **Body-part enum migration:** the existing censorship code in `NakedDesireCharacter::OnClothingEquip` / `OnClothingUnequip` reads `EPrivateBodyPartType`. Migration to `EBodyPart` must update that path or the censorship visibility will silently desync.
- **Slot-vocabulary reshape will break BP references** — coordinate before renaming/adding values in `EClothingSlotType`. The character constructor creates skeletal-mesh components keyed to those names; expect BPs in `Content/Blueprints/Player` to need a touch.
- **Toy items vs. clothing items:** the `UClothingItem` schema (coverage, `CanExpose`, `IsRestrictive`) doesn't fit sex toys cleanly. Strongly recommend a separate `USexToyItem : UItemInstance` (with `LustModifier`, `EmbarrassmentModifier`, `PulseModifier`, `VibrationAudible` fields) rather than overloading `UClothingItem`. Decide before Phase 1 toy work begins.
- **XP is a single shared pool** (GDD §5, §7.10). Do not introduce per-path XP counters in Phase 9 or earlier — a path's level is derived from how much the player has invested into that path's attribute pool.
- **Food buff hookpoints belong in Phase 4** (attribute multiplier interface), even though the food items themselves ship in Phase 10. Leave the seams; don't retrofit later.
- **Hunger hookpoint also belongs in Phase 4.** Add `effectiveMaxEnergy` + decay tick when adding Lust / Pulse / etc., so Phase 10's food items only need to call an existing reset method.
- **~~README contradiction — follower income cadence.~~ RESOLVED (2026-05-30, §20 #25).** Reconciled in favor of §7.9's **daily** model: follower income accrues and auto-deposits daily at the 04:00 day-roll, distinct from the weekly rent charge. README §9.4 / §13.3 / §15.1 and §20 #25 are updated. Phase 5 wires the payout on `OnDayChanged` (blocked on the Phase 8 follower count — pays 0 until then).
- **README contradiction — hiding spots vs sleep loss.** §6.3.4's new "Hiding spots" bullet says items have a *chance* of theft after sleep, but §4.4 says sleep = guaranteed loss of anything outside. Phase 10 theft work must wait until the GDD resolves whether hiding spots are an explicit exception to the sleep rule or only affect in-session theft chance.
- **~~README contradiction — masturbation gating.~~ RESOLVED (2026-05-30, §20 #26).** Home masturbation is always available to every player; *in-session* (public) masturbation is the Slut-path unlock. README §5.1 / §7.2 / §6.7 / §13.4 / §14.1 and §20 #26 are updated. Phase 4 / VS-2 implementation: gate only the in-session quick-action entry on Slut investment.
- **Stale §5.3 Slave path text** in the README still says "(cuffs require NPC help to remove)" — contradicts the Key + minigame removal flow. Phase 6 / Phase 10 work should not implement an NPC removal path; if encountered, treat it as stale documentation.
- _empty beyond this point_
---
## NPC Lively-City — Editor / Blueprint Handoff (added 2026-06-01)
> Self-contained checklist + behavior-tree design for finishing the NPC crowd + Walker/Stalker behavior **in-editor on another machine**. The C++ foundation already exists in `Source/NakedDesire/NPC/`: `NPCType.h` (`ENPCType`), `NPCTypeDefinition` (DataAsset), `ANPC` accessors + pooling hooks, `NPCDirectorSubsystem` + `NPCDirectorConfig`, and per-observer observation weight in `StatsManager`. Everything below is editor/Blueprint work **except** the one flagged code prerequisite.
### 0. Prerequisite — perception → blackboard wire ✅ DONE (2026-06-01)
`ANPCAIController::OnTargetPerceptionUpdate` now writes the `Player` blackboard key on each sight transition (set when sensed, cleared when lost) and resets `bHasReacted` on loss, so the BT below can branch. **The blackboard keys `Player` (Object) and `bHasReacted` (Bool) must exist in `BB_NPC` with those exact names** or the writes silently no-op.
### 1. Data assets to author
- [ ] **`DA_Walker`** (`UNPCTypeDefinition`): `Type=Walker`, `ObservationWeight≈0.35`, `bStopsToObserve=false`, `ReactionMontage`=quick glance/head-turn. (Tune weight against the VS-1 dial.)
- [ ] **`DA_Stalker`**: `Type=Stalker`, `ObservationWeight≈1.5`, `bStopsToObserve=true`, `ObserveDurationSeconds≈5`, `ReactionMontage`=notice/stare.
- [ ] **`DA_NPCDirector`** (`UNPCDirectorConfig`): set `MaxNPCs` (≥ largest target), `TargetCountDay`/`TargetCountNight`, `SpawnRadiusMin`/`Max`, `DespawnRadius` (> Max), `UpdateInterval` (~0.5s), and a weighted `SpawnTable` (Walker class high weight, Stalker low). Tune all values.
### 2. NPC blueprint classes
- [ ] **`BP_NPC_Walker` / `BP_NPC_Stalker`** (subclasses of `ANPC`): assign the matching `NPCTypeDefinition`, the final lightweight **skeletal mesh + standard locomotion AnimBP** (no MetaHuman, no motion matching). URO/`OnlyTickPoseWhenRendered` is already set in C++.
- [ ] Ensure `BP_NPCController` (the `ANPCAIController` BP) does **not** auto-run the BT on possess — the pool hooks (§5) own BT start/stop.
### 3. GameInstance
- [ ] On the project GameInstance BP (`UNakedDesireGameInstance`), assign `DA_NPCDirector` to the **`NPCDirector`** property (same place `CommissionBoard` is set).
### 4. Level
- [ ] **Remove `BPA_NPCSpawner`** from the level — `UNPCDirectorSubsystem` replaces it (world subsystem, auto-instantiated, no placement).
- [ ] Place `BP_NPCTargetLocation` wander destinations around the streets; confirm the **NavMesh** covers the playable area (the director spawns on reachable nav points).
### 5. Pool lifecycle (events on `BP_NPC_*`)
- [ ] **`OnActivatedFromPool`** → `Run Behavior Tree (BT_NPC)`, set `bHasReacted=false`, optionally pick an initial destination.
- [ ] **`OnDeactivatedToPool`** → `Stop Logic` on the brain + `Clear Focus` (so hidden/pooled NPCs don't tick AI).
### 6. Behavior tree (`BB_NPC` / `BT_NPC`)
**Blackboard keys:**
| Key | Type | Purpose |
|-----|------|---------|
| `Player` | Object | Perceived player pawn — set/cleared by the §0 code wire. Drives the react branch. |
| `TargetLocation` | Object *(or Vector)* | Current wander destination (`BP_NPCTargetLocation`). |
| `SpawnLocation` | Vector | Leash/home point (already used). |
| `bHasReacted` | Bool | Whether the notice reaction already played this sighting (prevents replay). |
**Tree shape:**
```
Root
└── Selector "what should I do"
├── [A] Sequence "stop & observe" ← Stalker-type only
│ decorators:
│ • Blackboard: Player Is Set (Observer Aborts: Both)
│ • ShouldStopToObserve == true (BP decorator, calls pawn fn)
│ children:
│ ├── BTT_PlayNoticeReaction (guard: bHasReacted == false → play montage, set true)
│ ├── BTT_StopMovement
│ ├── BTT_StareAtPlayer (SetFocus = Player, face them) [exists]
│ └── BTT_Wait (GetObserveDuration) then ClearFocus
└── [B] Sequence "wander" (loop) ← default; Walkers live here
service: BTS_NoticePlayer (glance-while-walking for Walkers)
children:
├── BTT_PickDestination → sets TargetLocation
├── MoveTo TargetLocation [built-in]
└── BTT_Wait (random idle 14s) (+ optional look-around / check-phone)
```
**Node notes:**
- **`[A]` decorators are the entire Walker/Stalker fork.** `Player Is Set` + `ShouldStopToObserve()==true` → only Stalkers (and future Snitch/Blogger) enter; Walkers fail the second decorator and fall through to wander. Set **Observer Aborts: Both** on `Player Is Set` so a Stalker interrupts its walk the instant it sees the player and resumes wandering when the player leaves.
- **`ShouldStopToObserve` decorator** = a **Blueprint decorator** (`BTDecorator_BlueprintBase`): controlled pawn → cast `ANPC` → return `ShouldStopToObserve()`. No C++ — the accessor is already `BlueprintPure`.
- **`BTT_StareAtPlayer`** (exists): `SetFocus(Player)` / `RotateToFaceBBEntry`. Follow with **Wait** whose duration = `GetObserveDuration()` (read in a tiny BP task), or fold the whole stop+face+wait+clear into one `BTT_Observe`.
- **`BTS_NoticePlayer`** (service on `[B]`, ~0.3s): if `Player Is Set` and `bHasReacted==false` → play `NPCTypeDefinition.ReactionMontage` (glance) and set `bHasReacted=true`. This gives **Walkers a reaction without stopping** (MoveTo keeps running). Stalkers react in `[A]` instead.
- **`BTT_PickDestination`**: gather all `BP_NPCTargetLocation` actors → pick random/nearest-unvisited → set `TargetLocation`. (EQS or a random reachable nav point near `SpawnLocation` are fine alternatives.)
**Why embarrassment needs no BT code:** the BT never touches `StatsManager`. A Stalker entering `[A]` stops and faces the player → sustains line-of-sight → the controller's perception keeps the player in its observer set → `ComputeObservedExposureRate` climbs at the Stalker's high `ObservationWeight`. A Walker passes with brief/broken LOS at low weight. Behavior and the dial reinforce each other automatically.
### 7. Compile & verify
- [ ] Build the C++ module (after the §0 change); fix any errors.
- [ ] Walk a navmeshed greybox street: ~1020 NPCs wander believably and varied; Stalkers stop & stare, Walkers glance & pass.
- [ ] Stand nude near a Stalker → embarrassment climbs faster than near a Walker (observation weight) and faster than fully clothed (coverage, VS-1).
- [ ] Cross the spawn ring repeatedly → no hitch (pooling), no in-view pop (off-camera spawn).
- [ ] Day→night phase change visibly changes density.