516 lines
88 KiB
Markdown
516 lines
88 KiB
Markdown
# 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 ~6–10 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:00–20:00, NPC density gated by phase (fix the `09–21` 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 2–3 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.** ~8–12 items across slots, authored from fully-covering → revealing → underwear so the VS-1 dial is actually testable, plus 1–2 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.
|
||
- **3–5 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 `09–21` 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:00–20: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 (1–2 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 (1–2 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 (3–4 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:00–20:00`. (The old `NPCSpawner.cpp:40` `09–21` 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 (1–2 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 (1–2 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 (3–4 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.1–5.3) and on path-tagged commission templates (§7).
|
||
- Author the vertical-slice content set from GDD §18.1 (15–20 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 1–4s) (+ 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: ~10–20 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.
|