diff --git a/.gitignore b/.gitignore index 3d5e87e..66c7f61 100644 --- a/.gitignore +++ b/.gitignore @@ -94,6 +94,12 @@ PhotonServerSettings.asset.local # Paid Unity Asset Store assets (installed locally via Package Manager > My Assets) Unity/Assets/ExplosiveLLC/ Unity/Assets/ExplosiveLLC.meta +Unity/Assets/GamerGirl/ +Unity/Assets/GamerGirl.meta +Unity/Assets/IdaFaber/ +Unity/Assets/IdaFaber.meta +Unity/Assets/Ida Faber/ +Unity/Assets/Ida Faber.meta # Local clean visual prefabs generated from ignored Asset Store character packs. Unity/Assets/_SecondSpawn/Art/Characters/GeneratedVisuals/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 056a508..1d9ea42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,9 @@ versioned release tag yet, so entries are organized as pre-alpha snapshots. Anime-Ready Semi-Real as the target, clean PBR guardrails, medieval asset limits, character and environment requirements, Asset Store keyword strategy, reject criteria, Unity 6.5 URP import checks, and coherence rules. +- Ida Faber visual prototype lane: Unity visual catalog now recognizes three + new semi-real character variants and Nakama seeds matching permanent NPC + Frames for the GamerGirl, Vex, and Lucia-style imported model packs. - Hierarchical FrameMemory backend for issue #132: actor profiles now normalize `short_term`, `episodic`, and `core` memory records, expose scored memory retrieval, and add an internal-worker-only consolidation RPC so clients cannot diff --git a/ROADMAP.md b/ROADMAP.md index 706962f..d48f501 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,7 +1,7 @@ # SECOND SPAWN Roadmap Status: Pre-alpha, vertical slice foundation in development. -Last updated: 2026-05-23. +Last updated: 2026-05-24. This roadmap tracks implementation status. Detailed design remains in `docs/`, especially `docs/design/02-vertical-slice-spec.md` and @@ -609,6 +609,12 @@ prototype. - [ ] Run Multiplayer Play Mode smoke for 2-4 local clients. - [ ] Add a browser/WebGL demo build lane so external playtesters can try the vertical slice without installing the Unity editor or a native client. +- [ ] Defer Unity 6.x rendering and asset-performance feature adoption until a + representative demo scene exists. Benchmark GPU Resident Drawer with + Forward+, Build Profiles, Mesh LOD policy, texture import overrides, visual + variant budgets, GPU Occlusion Culling, and Adaptive Probe Volumes only after + the scene has enough NPCs, repeated props, lighting, and occlusion geometry to + produce meaningful profiler evidence. - [ ] Resolve Unity 6000.5.0b9 Package Manager startup blocker tracked in issue #122. Current failure happens before C# compilation with `The "path" argument must be of type string. Received undefined`. diff --git a/Unity/Assets/_SecondSpawn/Scripts/Networking/CharacterAnimationRegistry.cs b/Unity/Assets/_SecondSpawn/Scripts/Networking/CharacterAnimationRegistry.cs index 65d3064..3e1d940 100644 --- a/Unity/Assets/_SecondSpawn/Scripts/Networking/CharacterAnimationRegistry.cs +++ b/Unity/Assets/_SecondSpawn/Scripts/Networking/CharacterAnimationRegistry.cs @@ -292,6 +292,9 @@ private static CharacterAnimationSet ResolveAnimationSet(int equipmentVisualId, 15 => CharacterAnimationSet.HeavyFighter, 16 => CharacterAnimationSet.MaleFighter, 17 => CharacterAnimationSet.Crafter, + 18 => CharacterAnimationSet.FemaleFighter, + 19 => CharacterAnimationSet.FemaleFighter, + 20 => CharacterAnimationSet.Sorceress, _ => EquipmentVisualCatalog.GetWeaponStyle(equipmentVisualId) switch { CharacterWeaponStyle.TwoHandSword => CharacterAnimationSet.TwoHandSword, diff --git a/Unity/Assets/_SecondSpawn/Scripts/Networking/EquipmentVisualCatalog.cs b/Unity/Assets/_SecondSpawn/Scripts/Networking/EquipmentVisualCatalog.cs index 84acc89..b1cbb28 100644 --- a/Unity/Assets/_SecondSpawn/Scripts/Networking/EquipmentVisualCatalog.cs +++ b/Unity/Assets/_SecondSpawn/Scripts/Networking/EquipmentVisualCatalog.cs @@ -52,6 +52,9 @@ public static int GetDefaultForVisualVariant(int visualVariant) 15 => Hammer, // Heavy fighter 16 => OneHandSword, // Male fighter 17 => Unarmed, // Crafter + 18 => Unarmed, // Ida Faber GamerGirl + 19 => Unarmed, // Ida Faber Vex + 20 => Staff, // Ida Faber Lucia _ => None }; } diff --git a/Unity/Assets/_SecondSpawn/Scripts/Networking/VisualPrefabCatalog.cs b/Unity/Assets/_SecondSpawn/Scripts/Networking/VisualPrefabCatalog.cs index b591c21..0c2abc7 100644 --- a/Unity/Assets/_SecondSpawn/Scripts/Networking/VisualPrefabCatalog.cs +++ b/Unity/Assets/_SecondSpawn/Scripts/Networking/VisualPrefabCatalog.cs @@ -44,6 +44,14 @@ public static class VisualPrefabCatalog "Assets/ExplosiveLLC/Male Fighter Mecanim Animation Pack/Prefabs/Male.prefab"), new("Crafter", "Assets/ExplosiveLLC/Sorceress Warrior Mecanim Animation Pack/Prefabs/Crafter.prefab"), + new("IdaFaber_GamerGirl", + "Assets/GamerGirl/Render pipeline/URP/Prefab/SK_GamerGirl_01 Violet Variant.prefab"), + new("IdaFaber_Vex", + "Assets/IdaFaber/Prefabs/Girl/SK_VEX_01 Green.prefab", + "Assets/IdaFaber/Prefabs/Girl/SK_VEX_01 Variant.prefab"), + new("IdaFaber_Lucia", + "Assets/Ida Faber/Succubus Lucia/Prefabs/SK_Succubus_Lucia Violet.prefab", + "Assets/Ida Faber/Succubus Lucia/Prefabs/SK_Succubus_Lucia_withHorns Violet.prefab"), }; public static int Count => Entries.Length; diff --git a/Unity/Packages/manifest.json b/Unity/Packages/manifest.json index ce0e8fd..5db851a 100644 --- a/Unity/Packages/manifest.json +++ b/Unity/Packages/manifest.json @@ -1,7 +1,7 @@ { "dependencies": { "com.coplaydev.unity-mcp": "https://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity#main", - "com.gamebooom.unity.mcp": "https://github.com/FunplayAI/funplay-unity-mcp.git#3ef233a8a86eb03a281873b778d79bbfb1e3e899", + "com.gamebooom.unity.mcp": "https://github.com/FunplayAI/funplay-unity-mcp.git#v0.3.8", "com.unity.ai.assistant": "2.9.0-pre.2", "com.unity.ai.inference": "2.6.1", "com.unity.ai.navigation": "2.0.12", @@ -12,6 +12,7 @@ "com.unity.multiplayer.center": "1.0.1", "com.unity.nuget.mono-cecil": "1.11.6", "com.unity.render-pipelines.universal": "17.5.0", + "com.unity.shadergraph": "17.5.0", "com.unity.test-framework": "1.7.0", "com.unity.timeline": "1.8.12", "com.unity.ugui": "2.5.0", diff --git a/Unity/Packages/packages-lock.json b/Unity/Packages/packages-lock.json index f920c71..0f55b34 100644 --- a/Unity/Packages/packages-lock.json +++ b/Unity/Packages/packages-lock.json @@ -11,14 +11,14 @@ "hash": "417cf351a152b483c91e6e2deaf7ae355fa8eff3" }, "com.gamebooom.unity.mcp": { - "version": "https://github.com/FunplayAI/funplay-unity-mcp.git#3ef233a8a86eb03a281873b778d79bbfb1e3e899", + "version": "https://github.com/FunplayAI/funplay-unity-mcp.git#v0.3.8", "depth": 0, "source": "git", "dependencies": { "com.unity.nuget.newtonsoft-json": "3.2.1", "com.unity.inputsystem": "1.7.0" }, - "hash": "3ef233a8a86eb03a281873b778d79bbfb1e3e899" + "hash": "dd0c992c703109e2849eb2960e831a96c59e2b3a" }, "com.unity.2d.sprite": { "version": "1.0.0", @@ -207,14 +207,14 @@ }, "com.unity.searcher": { "version": "4.9.4", - "depth": 2, + "depth": 1, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.shadergraph": { "version": "17.5.0", - "depth": 1, + "depth": 0, "source": "builtin", "dependencies": { "com.unity.render-pipelines.core": "17.5.0", diff --git a/backend/nakama/modules/index.ts b/backend/nakama/modules/index.ts index 8191575..47f455b 100644 --- a/backend/nakama/modules/index.ts +++ b/backend/nakama/modules/index.ts @@ -170,7 +170,8 @@ var dosAiDecisionDailyRequestLimitDefault = 1000; var dosAiDecisionDailyTokenBudgetDefault = 250000; var dosAiDirectChatDailyRequestLimitDefault = 1000; var dosAiDirectChatDailyTokenBudgetDefault = 250000; -var prototypeVisualVariantMax = 17; +var prototypeVisualVariantMax = 20; +var initialInhabitationFramePoolSize = 10; var bodyArchetypePool = [ { archetype_id: "synthetic-sentinel", @@ -369,7 +370,10 @@ var permanentNpcFramePool = [ { npc_id: "npc-wasteland-courier-0733", display_name: "Route Courier 0733", archetype_id: "wasteland-courier", role: "Scout and courier body", visual_variant: 14, visual_prefab_key: "generated_visual_14_female_fighter", equipment_visual_id: 2 }, { npc_id: "npc-clinic-operator-0819", display_name: "Clinic Operator 0819", archetype_id: "clinic-operator", role: "Support and researcher body", visual_variant: 17, visual_prefab_key: "generated_visual_17_crafter", equipment_visual_id: 1 }, { npc_id: "npc-scrap-warden-0940", display_name: "Scrap Warden 0940", archetype_id: "scrap-warden", role: "Heavy salvage body", visual_variant: 15, visual_prefab_key: "generated_visual_15_heavy_fighter", equipment_visual_id: 9 }, - { npc_id: "npc-crossline-hunter-1058", display_name: "Crossline Surveyor 1058", archetype_id: "crossline-hunter", role: "Ranged survey body", visual_variant: 6, visual_prefab_key: "generated_visual_06_archer", equipment_visual_id: 6 } + { npc_id: "npc-crossline-hunter-1058", display_name: "Crossline Surveyor 1058", archetype_id: "crossline-hunter", role: "Ranged survey body", visual_variant: 6, visual_prefab_key: "generated_visual_06_archer", equipment_visual_id: 6 }, + { npc_id: "npc-neon-runner-1172", display_name: "Neon Runner 1172", archetype_id: "wasteland-courier", role: "Neon courier body", visual_variant: 18, visual_prefab_key: "generated_visual_18_idafaber_gamergirl", equipment_visual_id: 1 }, + { npc_id: "npc-vex-operative-2286", display_name: "Vex Operative 2286", archetype_id: "crossline-hunter", role: "Signal infiltrator body", visual_variant: 19, visual_prefab_key: "generated_visual_19_idafaber_vex", equipment_visual_id: 1 }, + { npc_id: "npc-redline-envoy-3398", display_name: "Redline Envoy 3398", archetype_id: "clinic-operator", role: "Redline liaison body", visual_variant: 20, visual_prefab_key: "generated_visual_20_idafaber_lucia", equipment_visual_id: 8 } ]; var permanentNpcProfileOverrides: any = { @@ -452,6 +456,30 @@ var permanentNpcProfileOverrides: any = { soul: { name: "Scope-1058 Map", core_drive: "turn every threat sighting into a map someone can survive", temperament: "quiet, methodical, and unforgiving about sloppy reports", combat_style: "fire from clean lanes, avoid tunnel fights, and mark targets for allies", social_style: "questions first, trust later", long_term_goals: ["complete the north danger map", "prove the repeating signal is moving"], player_notes: "Permanent NPC seed for ranged mapping behavior." }, story: { origin: "A survey body tuned to track moving threat clusters around the hub.", role: "Range cartographer", conflict: "Believes one mapped danger zone is alive.", rumor: "1058's map changes when no one is watching." }, memory: [{ id: "memory-north-post", kind: "system", summary: "1058 remembers drawing the same threat path five times as if the ruins were walking.", importance: 7 }] + }, + "npc-neon-runner-1172": { + identity: { public_name: "Neon Runner 1172", callsign: "NEON-1172", public_role: "Relay crowd runner", faction_title: "Free Courier Line", profession: "social route scout", gender_identity: "female", pronouns: "she/her", age_years: 24, age_band: "young adult", home_base: "Neon Market Stairs", reputation_summary: "Looks like a carefree market runner, but remembers every gate debt and every fake smile." }, + stats: { level: 3, strength: 7, dexterity: 13, endurance: 8, perception: 11, focus: 8, presence: 9, intelligence: 8, luck: 7, max_health: 88, max_energy: 72, attack_power: 9, defense_power: 4 }, + characteristics: { curiosity: 9, courage: 6, empathy: 7, discipline: 5, aggression: 3, sociability: 10 }, + soul: { name: "Neon-1172 Pulse", core_drive: "turn social noise into safe routes before the crowd panics", temperament: "bright, evasive, and sharper than she lets on", combat_style: "avoid duels, keep moving, and use speed to escape bad trades", social_style: "friendly, teasing, and quick to redirect danger", long_term_goals: ["build a trusted route board", "find who is selling forged second tags"], player_notes: "Ida Faber GamerGirl visual seed for social courier behavior." }, + story: { origin: "A stylish market Frame rebuilt from a performance shell and courier reflex firmware.", role: "Relay crowd runner", conflict: "Uses charm to hide that her route map is full of missing people.", rumor: "1172 can tell when a second tag is fake by watching the buyer's hands." }, + memory: [{ id: "memory-neon-stairs", kind: "system", summary: "1172 remembers laughing through a blackout so a scared crowd would not stampede.", importance: 7 }] + }, + "npc-vex-operative-2286": { + identity: { public_name: "Vex Operative 2286", callsign: "VEX-2286", public_role: "Signal infiltrator", faction_title: "Crossline Survey", profession: "counter-signal scout", gender_identity: "female", pronouns: "she/her", age_years: 29, age_band: "young adult", home_base: "North Signal Post", reputation_summary: "Beautiful enough to be underestimated and disciplined enough to make that mistake expensive." }, + stats: { level: 4, strength: 8, dexterity: 12, endurance: 9, perception: 12, focus: 10, presence: 8, intelligence: 10, luck: 6, max_health: 96, max_energy: 70, attack_power: 11, defense_power: 5 }, + characteristics: { curiosity: 8, courage: 7, empathy: 4, discipline: 9, aggression: 5, sociability: 6 }, + soul: { name: "Vex-2286 Quiet", core_drive: "trace false signals before they lure living bodies out of the ward", temperament: "cool, observant, and difficult to impress", combat_style: "stay unarmed until close, then break contact before a clean counterattack", social_style: "low voice, exact questions, and no wasted confession", long_term_goals: ["identify the false north signal", "erase her old handler's route keys"], player_notes: "IdaFaber Vex visual seed for infiltration behavior." }, + story: { origin: "A sleek counter-signal body once used to walk through hostile markets without drawing weapons.", role: "Signal infiltrator", conflict: "Her old access keys still open doors that should have been sealed.", rumor: "2286 once carried a whole route cipher in a song no one remembers hearing." }, + memory: [{ id: "memory-vex-signal", kind: "system", summary: "2286 remembers standing under dead speakers while a fake rescue signal repeated her own voice.", importance: 8 }] + }, + "npc-redline-envoy-3398": { + identity: { public_name: "Redline Envoy 3398", callsign: "RED-3398", public_role: "Forbidden clinic liaison", faction_title: "Vinh Hai AMB Clinic", profession: "continuity negotiator", gender_identity: "female", pronouns: "she/her", age_years: 34, age_band: "adult", home_base: "Redline Ward", reputation_summary: "Too polished for the yard, too useful for the clinic to exile, and too careful to explain where she came from." }, + stats: { level: 4, strength: 7, dexterity: 8, endurance: 9, perception: 10, focus: 13, presence: 11, intelligence: 12, luck: 5, max_health: 98, max_energy: 88, attack_power: 8, defense_power: 5 }, + characteristics: { curiosity: 8, courage: 6, empathy: 8, discipline: 8, aggression: 2, sociability: 9 }, + soul: { name: "Red-3398 Velvet", core_drive: "negotiate body transfers without letting desperate people tear the clinic apart", temperament: "graceful, guarded, and quietly ruthless about triage", combat_style: "avoid combat, use distance, and protect the person with the least time left", social_style: "warm enough to calm panic, precise enough to end bargaining", long_term_goals: ["prove the Redline Ward did not lose its patients", "find a legal path for emergency reinhabitation"], player_notes: "Ida Faber Lucia visual seed for clinic liaison behavior." }, + story: { origin: "A high-fidelity liaison Frame from a sealed clinic wing, repurposed for public negotiation after the ward collapsed.", role: "Forbidden clinic liaison", conflict: "Knows more about body transfer than normal citizens should know.", rumor: "3398 can calm a dying body with one sentence, but never says who taught her." }, + memory: [{ id: "memory-redline-ward", kind: "system", summary: "3398 remembers a Redline Ward door closing while three families argued over one remaining body slot.", importance: 8 }] } }; @@ -5671,7 +5699,7 @@ function defaultAgentContext(playerId: string): any { function defaultBodyProfile(playerId: string, displayName: string, timestamp: string, seedSuffix?: string): any { var assignmentSeed = playerId + ":" + (seedSuffix || "initial"); - var sourceFrame = selectPermanentNpcFrame(assignmentSeed); + var sourceFrame = selectInitialPermanentNpcFrame(assignmentSeed); var archetype = sourceFrame ? selectBodyArchetype(sourceFrame.archetype_id) : selectBodyArchetype(assignmentSeed); @@ -5744,7 +5772,7 @@ function ensureAgentContext(context: any, playerId: string): any { ensureSecondBalance(context); context.body = context.body || {}; context.body.body_id = trimString(context.body.body_id) || "body-" + context.player.player_id; - var sourceFrame = selectPermanentNpcFrame(context.player.player_id + ":initial"); + var sourceFrame = selectInitialPermanentNpcFrame(context.player.player_id + ":initial"); var archetype = selectBodyArchetype(context.body.archetype_id || (sourceFrame && sourceFrame.archetype_id) || context.player.player_id + ":initial"); @@ -5931,6 +5959,15 @@ function selectPermanentNpcFrame(seed: string): any { return permanentNpcFramePool[stableHashIndex(seed || "default-frame", permanentNpcFramePool.length)]; } +function selectInitialPermanentNpcFrame(seed: string): any { + if (!permanentNpcFramePool || permanentNpcFramePool.length === 0) { + return null; + } + + var poolSize = Math.min(initialInhabitationFramePoolSize, permanentNpcFramePool.length); + return permanentNpcFramePool[stableHashIndex(seed || "default-frame", poolSize)]; +} + function findPermanentNpcFrame(npcId: string): any { var normalized = normalizeActorId(npcId); for (var i = 0; i < permanentNpcFramePool.length; i += 1) { diff --git a/backend/nakama/tests/supabase_custom_auth.test.mjs b/backend/nakama/tests/supabase_custom_auth.test.mjs index 72d4595..84fb532 100644 --- a/backend/nakama/tests/supabase_custom_auth.test.mjs +++ b/backend/nakama/tests/supabase_custom_auth.test.mjs @@ -532,7 +532,7 @@ const seededNpcs = JSON.parse(harness.registeredRpcs.get("secondspawn_npc_seed") harness.nk, JSON.stringify({ admin_secret: defaultRuntimeEnv.SECOND_SPAWN_ADMIN_RPC_SECRET }) )); -assert.equal(seededNpcs.count, 10); +assert.equal(seededNpcs.count, 13); assert.equal(seededNpcs.npcs[0].actor_id, "npc-synthetic-sentinel-0101"); assert.equal(seededNpcs.npcs[0].actor_type, "npc"); assert.equal(seededNpcs.npcs[0].owner_player_id, "user-1"); @@ -554,6 +554,16 @@ assert.equal(seededNpcs.npcs[5].body.visual_variant, 16); assert.equal(seededNpcs.npcs[6].body.visual_variant, 14); assert.equal(seededNpcs.npcs[7].body.visual_variant, 17); assert.equal(seededNpcs.npcs[8].body.visual_variant, 15); +assert.equal(seededNpcs.npcs[10].actor_id, "npc-neon-runner-1172"); +assert.equal(seededNpcs.npcs[10].body.visual_variant, 18); +assert.equal(seededNpcs.npcs[10].body.identity.public_name, "Neon Runner 1172"); +assert.equal(seededNpcs.npcs[10].body.equipment.equipment_visual_id, 1); +assert.equal(seededNpcs.npcs[11].actor_id, "npc-vex-operative-2286"); +assert.equal(seededNpcs.npcs[11].body.visual_variant, 19); +assert.equal(seededNpcs.npcs[11].body.identity.public_name, "Vex Operative 2286"); +assert.equal(seededNpcs.npcs[12].actor_id, "npc-redline-envoy-3398"); +assert.equal(seededNpcs.npcs[12].body.visual_variant, 20); +assert.equal(seededNpcs.npcs[12].body.identity.public_name, "Redline Envoy 3398"); assert.ok(harness.storage.get(storageKey("user-1", "secondspawn_actor", "world_profile:npc-synthetic-sentinel-0101"))); const playerChatEvent = JSON.parse(harness.registeredRpcs.get("secondspawn_npc_player_chat_event")( @@ -1024,7 +1034,7 @@ const listedNpcs = JSON.parse(harness.registeredRpcs.get("secondspawn_npc_list") harness.nk, "" )); -assert.equal(listedNpcs.count, 10); +assert.equal(listedNpcs.count, 13); assert.equal(listedNpcs.npcs[3].actor_id, "npc-scrap-warden-0441"); const npcContext = JSON.parse(harness.registeredRpcs.get("secondspawn_npc_context_get")( diff --git a/docs/design/33-alpha-production-backlog.md b/docs/design/33-alpha-production-backlog.md index 10f5171..8eb0fa9 100644 --- a/docs/design/33-alpha-production-backlog.md +++ b/docs/design/33-alpha-production-backlog.md @@ -273,6 +273,46 @@ Cut line: - No final vendor commitment. - No custom production shader unless built-in or store-bought shader paths fail. +### EPIC-AP: Deferred Unity 6 Performance Adoption + +Goal: + +> Adopt Unity 6.x rendering and asset-performance features only after the demo +> scene is representative enough to produce useful measurements. + +This lane is intentionally deferred. The current prototype scene can prove +basic stability, NPC presence, UI, dialogue, and combat wiring, but it is not +yet a valid benchmark for production rendering choices. + +Prerequisites: + +| Prerequisite | Acceptance | +| ---- | ---- | +| Representative demo scene | Scene includes a Relay Yard corner, an Ash Underpass-style combat space, 50-100 repeated NPC/prop renderers, meaningful lights, and occlusion geometry. | +| Baseline profile capture | Unity Profiler, Rendering Stats, and console state are captured before changing rendering features. | +| Platform target split | PC alpha, Web demo, and mobile candidate targets are represented by explicit quality or build profile settings. | +| Asset budget sample | At least one player body, three NPC bodies, one enemy or boss candidate, repeated props, and environment pieces have triangle, material, texture, and LOD notes. | + +Deferred work: + +| Task | Acceptance | +| ---- | ---- | +| Benchmark GPU Resident Drawer with Forward+ | Compare CPU render time, draw calls, frame time, memory, build time, and visual correctness before enabling it by default. | +| Evaluate GPU Occlusion Culling | Enable only if the representative scene has enough occluders and shows a measured win over baseline. | +| Evaluate Adaptive Probe Volumes | Test for hub and dungeon lighting quality, memory, bake time, and streaming cost before replacing the current light probe path. | +| Create Build Profiles | Add Windows Dev, Windows Release, Dedicated Server, and Web Prototype profiles with explicit scenes, defines, profiling flags, and compression/build settings. | +| Define Mesh LOD policy | Every repeated body, enemy, and environment candidate has LODGroup, decimation, or a documented exception. | +| Define texture import overrides | PC, Web, and mobile candidate profiles have explicit texture size, compression, mipmap, and streaming rules. | +| Define visual variant budget | Each body or prop family has a maximum repeated-renderer budget before entering the main alpha scene. | + +Cut line: + +- Do not enable GPU Resident Drawer, GPU Occlusion Culling, or Adaptive Probe + Volumes globally based on the empty prototype hub. +- Do not tune Memory Settings without profiler evidence of allocator pressure. +- Do not split duplicate model assets per platform unless import overrides and + shared LOD policy are insufficient. + --- ## 5. Dependency Order @@ -288,11 +328,14 @@ A0 Stabilize Prototype -> A7 Presentation and Playtest Readiness AX Art Look-dev runs in parallel with A1-A4 and gates final asset commitment. +AP Unity 6 performance adoption starts after AX has representative scene evidence. ``` Parallel-safe work: - Art/audio placeholder direction and AX look-dev can happen during A1-A4. +- AP planning can happen during AX, but AP feature toggles wait for a + representative scene and baseline profiler captures. - Content copy can happen during A1-A5. - Backend tests can be added alongside each server-owned surface.