# Prompt Architecture Improvement Plan This is a working research note for organizing the prompt builder around the routing map in `docs/prompt-pool-routing-map.md`. ## Current Branch Additions The current branch adds two major surfaces: - `SxCP Krea2 Resolution Selector` in `node_seed_resolution.py`, with README notes. - Expanded hardcore interaction/manual/action pools in `categories/sexual_poses.json`, `categories/expression_composition_pools.json`, `prompt_builder.py`, and `krea_formatter.py`. The map audit currently sees: - 15 sexual pose subcategories. - 94 sexual pose item templates. - 23 expression pools. - 24 composition pools. - A new Krea2 resolution node with width/height/API aspect outputs. ## Architectural Finding The project has a good functional map, but ownership is still mixed inside large files: - `prompt_builder.py` owns selection, character resolution, role graph logic, camera adaptation, pair assembly, and some final string cleanup. - `krea_formatter.py` owns metadata parsing, cast naturalization, sexual action rewriting, POV rewriting, clothing cleanup, camera preservation, fallback parsing, and final prose assembly. - `sdxl_formatter.py` owns tag assembly and style/quality presets. - `caption_naturalizer.py` owns training-caption prose. - Category JSON files own scalable pool content, but Python still owns several compatibility and role-graph decisions. The biggest maintainability risk is not the number of pools. The risk is that selection, semantic rewriting, and final text hygiene are too interleaved. When a prompt has wrong text, it is easy to patch the wrong layer. ## First Refactor Boundary Generic text hygiene now has one home: - `prompt_hygiene.py` It should only handle route-agnostic cleanup: - whitespace and punctuation normalization; - empty field-label removal; - repeated trigger prefix cleanup; - duplicate comma-list item removal; - adjacent duplicate sentence cleanup; - simple dangling connector cleanup. It must not make semantic decisions such as sexual action positioning, POV geometry, clothing state, or model-specific tag weighting. Those stay in the route-specific owner. It also preserves ordinary words such as `composition` inside normal sentences; empty field-label cleanup is limited to standalone labels. Formatter input/fallback parsing now has one home: - `formatter_input.py` It owns route-neutral parsing shared by Krea2, SDXL, and natural-caption routes: - whitespace and punctuation normalization before formatter parsing; - JSON row detection from `metadata_json` or source text; - trigger-prefix stripping with route-specific trigger candidate lists; - `Avoid:` positive/negative splitting for fallback text; - the shared prompt field-label inventory and extraction such as `Setting:`, `Sexual scene:`, `Camera control:`, or `Composition:`; - fallback field-label stripping for tag/text routes that need label-free body text; - row-value fallback from metadata fields to labeled prompt text. It must not make formatter-style decisions. Krea prose, SDXL tags, and training caption sentence shape stay in their formatter modules. Shared hardcore phrase cleanup now has one home: - `hardcore_text_cleanup.py` It owns environment-anchor normalization used by both prompt generation and Krea formatting, including malformed surface joins and bed/sheet/couch anchors that should become model-neutral body-support language. It must stay route-neutral: no Krea prose, no SDXL tags, and no category selection logic. Current integration points: - `prompt_builder.build_prompt` - `prompt_builder.build_insta_of_pair` - `krea_formatter.format_krea2_prompt` - `sdxl_formatter.format_sdxl_prompt` - `caption_naturalizer.naturalize_caption` ## Target Organization ### Generation Layer Owner: `prompt_builder.py` plus `categories/*.json`. Keep here: - category/subcategory/item selection; - seed axis routing; - character slot/profile resolution; - scene/expression/composition pool selection; - role graph creation from structured category axes; - metadata row construction. Move or isolate later: - pair assembly helpers that still live in `prompt_builder.py`. Already isolated: - single-prompt builder orchestration, including input normalization, seed-axis setup, built-in/custom row routing, legacy location/composition handling, camera application, and final prompt-row normalization, lives in `builder_prompt_route.py`; `prompt_builder.py` keeps the public wrapper. - config-driven prompt-builder request parsing, helper-node config mapping, and direct `build_prompt` kwarg assembly live in `builder_config_route.py`; `prompt_builder.py` keeps the public wrapper. - JSON category loading, subcategory normalization, named scene/expression/ composition pool loading, cast compatibility filtering, exact subcategory lookup, and inheritance-based pool merging live in `category_library.py`. - JSON `pool_extensions`, legacy pool patching, built-in category choice lists, and category/subcategory UI choices live in `category_extensions.py`. - object-style item-template metadata extraction, action/position family normalization, position-key normalization, and metadata audit errors live in `category_template_metadata.py`. - row item selection, weighted item/pair choice, item-template axis filling, and oral/outercourse axis compatibility filters live in `row_item.py`; `prompt_builder.py` keeps public delegate wrappers. - row category/subcategory/item route resolution lives in `row_category_route.py` behind `CategoryItemRoute`, covering hardcore position-category filtering, cast-count adjustment, pose-vs-content seed-axis choice, item metadata collection, legacy dict compatibility, and pose-category item sanitizing; `prompt_builder.py` keeps public delegate wrappers. - row prompt/caption template selection, safe formatting, default prompt templates, configured-cast descriptor insertion, and POV directive insertion live in `row_rendering.py`; `prompt_builder.py` keeps compatibility aliases. - row action/position route metadata resolution lives in `row_route_metadata.py` behind `ActionPositionRoute`, covering template metadata precedence, inferred position-key merging, legacy dict compatibility, and source action-family fallback; `prompt_builder.py` keeps public delegate wrappers. - built-in legacy row generation, auto-weighted/auto-full selection, row mode randomization, ratio clamps, and expression-intensity randomization live in `row_generation.py`; `prompt_builder.py` keeps public delegate wrappers. - category/cast route preset schemas, config JSON builders, choice lists, and parsers live in `category_cast_config.py`; `prompt_builder.py` keeps public delegate wrappers for existing nodes and tests. - generation-time cast count phrases, configured-cast context metadata, character-slot label assignment, scene-kind labels, cast-summary wording, and couple count normalization live in `cast_context.py`; `prompt_builder.py` keeps delegate wrappers where existing generation paths still call the old helper names. - row subject-context routing for single, couple, configured-cast, group, and layout subjects lives in `subject_context.py`; it combines appearance policy, cast metadata, and generator subject pools behind one row-facing entry point. - row subject route orchestration, character slot/profile precedence, configured-cast POV labels, visible cast descriptor collection, and descriptor prompt cleanup live in `row_subject_route.py`; `prompt_builder.py` keeps a public delegate wrapper. - ethnicity/filter choices, advanced filter JSON, ethnicity-list JSON, filter parsing, and ethnicity normalization live in `filter_config.py`; character routes and builder filters use `prompt_builder.py` delegate wrappers. - character choice lists, descriptor detail/presence/slot-seed normalization, characteristic-list JSON builders/parsers, eye labels, hair config builders/parsers, and hair phrase helpers live in `character_config.py`; `prompt_builder.py` keeps public delegate wrappers. - character slot JSON construction, character-cast parsing, slot normalization, slot summary text, slot expression override policy, slot seed helpers, and slot figure/ethnicity normalization live in `character_slot.py`; `prompt_builder.py` keeps public delegate wrappers. - generation-time subject appearance selection, normalized-slot context resolution, slot hair/outfit/clothing selection, character-context row application, and character-slot-to-profile-row conversion live in `character_appearance.py`; `prompt_builder.py` keeps public delegate wrappers. - character manual-detail config, profile name/path policy, profile JSON normalization, descriptor assembly, save/load/rename/delete operations, fallback profile loading, and context override application live in `character_profile.py`; `prompt_builder.py` only bridges generated slot rows into profile saves. - generation profile presets, override normalization, trigger policy, and profile config parsing live in `generation_profile_config.py`; `prompt_builder.py` keeps public delegate wrappers. - location/composition config presets, themed location packs, custom location/composition entry parsing, merge behavior, and config parsing live in `location_config.py`; built-in row location/composition config application, source metadata, and prompt/caption rewrites live in `row_location.py`. - row scene/expression/pose/composition pool routing, category inheritance, runtime location/composition pool overrides, and generator fallback pool selection live in `row_pools.py`; `prompt_builder.py` keeps public delegate wrappers. - row scene/pose/expression/composition axis selection lives in `row_prompt_axes.py` behind `PromptAxesRoute`, covering compatible-entry filtering, expression-disabled handling, per-character expression promotion, legacy dict compatibility, POV composition adaptation, and pose-category environment sanitizing; `prompt_builder.py` keeps public delegate wrappers. - row prompt/caption text-field resolution, prompt/caption template selection, safe formatting, configured-cast descriptor insertion, and POV directive insertion live in `row_rendering.py`; `prompt_builder.py` keeps public delegate wrappers. - row role-graph route sequencing lives in `row_role_graph.py`, covering hardcore source role graph construction, pose-category environment-anchor cleanup, and POV role-graph rewriting before prompt axes and formatter metadata consume the graph. - row expression text cleanup, expression route resolution, expression intensity weighting, character-slot/cast expression override resolution, and per-character expression picking plus action-aware character-expression sanitizing live in `row_expression.py`; `prompt_builder.py` keeps public delegate wrappers. - hardcore position/action-filter choices, selected-position normalization, config JSON builders/parsers, focus-policy toggles, subcategory allow-list policy, position-key detection, category filtering, and item-template/axis filtering live in `hardcore_position_config.py`. - hardcore configured-cast role graph generation lives in `hardcore_role_graphs.py`; row generation reaches it through `row_role_graph.py` after item/axis metadata is selected. - fallback role graph wording lives in `hardcore_role_fallback.py`, covering solo rows, women-only rows, men-only rows, mixed group fallbacks, and support partner sentences. - interaction-style role graph wording lives in `hardcore_role_interaction.py`, covering foreplay, manual stimulation, body worship, clothing transitions, dominant guidance, camera performance, aftercare, and group coordination. - outercourse-specific role graph wording has started moving into action-family modules; `hardcore_role_outercourse.py` owns boobjob, testicle-sucking, penis-licking, handjob, and footjob body geometry. - oral-specific role graph wording lives in `hardcore_role_oral.py`, including direct POV viewer phrasing for kneeling, face-sitting, sixty-nine, edge-supported, side-lying, chair, standing, and reclining oral positions. - penetration-specific role graph wording lives in `hardcore_role_penetration.py`, covering the main vaginal penetration position families while Krea POV rewriting keeps first-person geometry stable. - anal/double-contact role graph wording lives in `hardcore_role_anal.py`, covering rear-entry anal variants and front/back double-contact source geometry. - climax role graph wording lives in `hardcore_role_climax.py`, covering ejaculation aftermath placement for face/body/ass, lap, open-thigh, side-lying, and front/back group layouts. - camera option schema, orbit/Qwen translation, config parsing, camera directive text, and camera caption text live in `camera_config.py`; camera-scene prose lives in `scene_camera_adapters.py`; row-level camera insertion, contextual coworking composition mutation, subject-kind detection, and POV suppression live in `row_camera.py`. - shared POV slot detection, label merging/filtering, builder-side POV directives, source role-graph viewer replacement, and shared composition cleanup live in `pov_policy.py`; prompt builder and Krea POV routes delegate to it. - shared hardcore environment-anchor cleanup lives in `hardcore_text_cleanup.py` and normalizes malformed pool joins before metadata reaches formatter routes. - shared hardcore action metadata lives in `hardcore_action_metadata.py`; custom rows now emit `action_family`, `position_family`, `position_key`, and `position_keys` so formatter routing and debugging do less keyword guessing. Krea, SDXL, and training-caption routes consume these fields when present. - shared row route metadata readers live in `route_metadata.py`, covering normalized action family, position family/keys, and route-specific formatter hints for Krea, SDXL, and training-caption routes. Position keys are strict by default, while SDXL can opt into legacy unknown key tags for compatibility. - final row and pair text normalization lives in `row_normalization.py`, covering trigger prepending, extra-positive append, negative merge/dedupe, caption-part joining, embedded soft/hard row output synchronization, and row sanitation before metadata leaves generation. It also copies side-specific pair metadata, such as soft partner styling and hardcore clothing/detail state, onto the embedded soft/hard rows. - final custom-row assembly now lives in `row_assembly.py` behind `CustomRowAssemblyRequest`, covering render context population, prompt/caption rendering delegation, row-base indexing, row metadata copying, configured-cast count metadata, profile/slot metadata, and disabled-expression cleanup. ### Pair / Adapter Layer Owner today: `pair_builder.py`; `prompt_builder.build_insta_of_pair` is the public wrapper used by the node layer. Keep here: - the public wrapper signature and dependency bridge needed by existing nodes and tests. Already isolated: - Insta/OF option normalization, softcore category/outfit/pose pools, partner outfit pools, clothing-continuity labels, negatives, and hardcore cast count policy, plus hardcore detail-density directive text, live in `pair_options.py`; `prompt_builder.py` keeps public delegate wrappers for existing nodes and tests. - pair route sequencing now lives in `pair_builder.py` behind `InstaPairBuildRequest` and `InstaPairBuildDependencies`, covering option/filter/seed/cast parsing handoff, soft/hard row orchestration, cast context, camera route, clothing route, and final output assembly delegation. - soft/hard row creation lives in `pair_rows.py` behind `InstaPairRowsRoute`, including softcore expression override resolution, Woman A slot context application, soft outfit/pose overrides, POV row fields, hardcore row creation, and legacy dict compatibility. - pair-level cast/display context lives in `pair_cast.py`, including descriptor prose, descriptor-entry assembly, shared descriptors, cast-label cleanup, same-cast softcore descriptor text, partner styling, platform and level labels, softcore cast presence text, and hard cast summary text. - pair-level camera routing lives in `pair_camera.py` behind `InstaPairCameraRoute`, including soft/hard camera config selection, same-as-softcore mode, camera-detail override, same-room hard scene continuity, camera-aware composition mutation, POV camera suppression, row/root camera metadata synchronization, and legacy dict compatibility. - pair-level clothing policy lives in `pair_clothing.py` behind `HardcorePairClothingRoute`, including clothing sentence formatting, body-exposure scene cleanup, action-aware body-access flags, conflicting outfit-piece cleanup, default visible-men clothing, character-clothing override handling, hardcore clothing continuity, final root clothing-state assembly, and legacy dict compatibility. - final pair output assembly lives in `pair_output.py`, including soft/hard prompt strings, trigger preservation, negatives, captions, and root metadata shape; the final cleanup step is delegated to `row_normalization.py`. Embedded soft/hard rows are synchronized to the final pair prompt, caption, and negative outputs during normalization so serialized pair metadata does not carry stale standalone row text. Side-specific structured fields are synchronized there too, including soft partner styling and hardcore clothing continuity metadata. ### Krea2 Formatter Path Owner: `krea_formatter.py`. Keep here: - Krea prose style; - Krea top-level route orchestration; - camera-scene preservation; - fallback text parsing. Already isolated: - `krea_format_route.py` owns top-level Krea dispatch, including option normalization, metadata-vs-text input selection, single-vs-pair branching, extra positive/negative merging, final prose hygiene, and output shape; `krea_formatter.py` keeps the public wrapper. - `krea_configured_cast_formatter.py` owns normal metadata configured-cast Krea prose assembly behind `KreaConfiguredCastRequest`, `KreaConfiguredCastDependencies`, and `KreaConfiguredCastPrompt`; `krea_formatter.py` keeps configured-cast detection and compatibility wrapper helpers. - `krea_normal_formatter.py` owns normal metadata single/couple/generic Krea prose assembly behind `KreaNormalRowRequest`, `KreaNormalRowDependencies`, and `KreaNormalRowPrompt`; `krea_formatter.py` keeps route selection. - `krea_row_fields.py` owns shared normal-row Krea field extraction for item, scene, pose, expression, composition/source-composition, camera, and style so normal and configured-cast Krea routes cannot drift independently. - `krea_pair_formatter.py` owns Insta/OF pair soft/hard Krea prose assembly behind `KreaPairFormatRequest`, `KreaPairFormatDependencies`, and `KreaPairPrompts`; `krea_formatter.py` keeps the `_insta_pair_to_krea` compatibility wrapper. - `krea_cast.py` owns cast descriptor parsing, cast labels, cast prose, label joining, natural cast descriptor text, and label replacement for formatter routes, including the caption naturalizer's cast metadata path. - `krea_clothing.py` owns clothing-state cleanup and action-aware body-access wording for formatter routes. - `krea_action_context.py` owns shared action-family predicates, axis context text, climax detection, and detail-density normalization used by action and POV formatter routes. - `hardcore_action_metadata.py` owns shared action-family constants, normalization, and inference used by the builder and Krea formatter route. - `pov_policy.py` owns shared POV labels, label filtering, source role-graph viewer replacement, and composition cleanup; `krea_pov.py` owns Krea-specific POV camera support text while delegating shared POV policy. - `krea_detail.py` owns generic detail-clause splitting, deduping, joining, and density limiting for Krea action prose. - `krea_action_positions.py` owns non-POV pose anchors, body-arrangement text, rear-entry detection, and action-position phrasing. - `krea_action_details.py` owns non-climax item/detail cleanup for foreplay, outercourse, oral, penetration, toy/double-contact, and anchor dedupe paths. - `krea_action_climax.py` owns climax-specific role/detail cleanup and aftermath view dedupe. - `krea_action_dispatch.py` owns non-POV role normalization, action-family classification, and family-specific detail cleanup. - `krea_actions.py` owns final non-POV hardcore action sentence assembly. - `krea_pov_actions.py` owns POV hardcore action sentence rewriting, first-person body geometry, and selected-position-axis priority before loose context fallback. - `formatter_input.py` owns shared metadata/source JSON detection, trigger stripping, the shared prompt field-label inventory, prompt-field extraction, `Avoid:` splitting, and row-value fallback for Krea, SDXL, and caption routes. - `route_metadata.py` owns shared row-level action-family, position-family, position-key, and formatter-hint reads so formatter routes do not normalize these fields independently. Improve later: - keep adding route-level smoke fixtures when new metadata fields start influencing formatter output; ### SDXL Formatter Path Owner: `sdxl_formatter.py`. Keep here: - trigger behavior; - style and quality presets; - final style/body/quality prompt assembly; - nude-weight setting; - negative-prompt assembly. Already isolated: - `sdxl_tag_routes.py` owns normal metadata row tags and Insta/OF pair soft/hard tag extraction behind `SDXLRowTagRequest`, `SDXLPairTagRequest`, `SDXLTagRouteDependencies`, and `SDXLTagRoute`; `sdxl_formatter.py` keeps compatibility wrappers plus final style/quality/trigger assembly. - `sdxl_tag_policy.py` owns SDXL tag splitting, tag-key dedupe, count inference, character descriptor tags, metadata-family hint tags, camera tags, explicit/nude helper tags, and route dependency assembly. - metadata-family tag hint data from `action_family`, `position_family`, and `position_keys` stays in `sdxl_presets.py` and is read by `sdxl_tag_policy.py`. - shared row route metadata reads from `route_metadata.py`. - shared formatter input parsing from `formatter_input.py`. - style presets, quality presets, default negative prompt, and action/position family tag hints from `sdxl_presets.py`. - formatter profiles for manual controls, Pony flat-vector, SDXL photo, and plain flat-vector styles live in `sdxl_presets.py` and are exposed by `SxCP SDXL Formatter`. - fallback field-label cleanup delegates to `formatter_input.py`. Improve later: - add route-level fixtures for any new SDXL model profile that needs different tag ordering. ### Naturalizer Path Owner: `caption_naturalizer.py`. Keep here: - top-level natural caption orchestration; - training-caption trigger behavior; - style-tail policy from `caption_policy.py`. Already isolated: - `caption_metadata_routes.py` owns metadata row natural-language assembly for single, couple, configured-cast, group/layout, and Insta/OF pair routes behind `CaptionMetadataRouteRequest`, `CaptionMetadataRouteDependencies`, and `CaptionMetadataRoute`; `caption_naturalizer.py` keeps compatibility wrappers, profile handling, trigger behavior, and text fallback. - `caption_text_policy.py` owns caption sentence helpers, trigger wrapping, formatter-hint append, row-value fallback wrappers, cast text wrappers, single-caption front parsing, route dependency assembly, and caption metadata helper callbacks used by `caption_metadata_routes.py`. - metadata-family action labels from `action_family` and `position_family` via `caption_policy.py`. - shared row route metadata reads from `route_metadata.py`. - shared formatter input parsing from `formatter_input.py`. - shared cast descriptor parsing and label replacement from `krea_cast.py`. - caption detail-level/style-policy normalization, clothing cleanup, and composition cleanup from `caption_policy.py`. - caption profiles for manual controls, concise training captions, dense training captions, and browsing captions live in `caption_policy.py` and are exposed by `SxCP Caption Naturalizer`. Improve later: - add more caption profiles if a new training or browsing workflow needs a distinct default. ### Category JSON Path Owner: `categories/*.json`. Keep here: - scalable prompt pool content; - named scene/expression/composition pools; - item templates and axes; - direct category-specific wording. - optional object-style item templates with route metadata such as `action_family`, `action_type`, `position_family`, `family`, `position_key`, `position_keys`, and `formatter_hint`; string templates remain valid and fall back to Python inference. Normalized formatter hints are routed into Krea, SDXL, and caption naturalization through `all` plus the matching formatter route only. Improve later: - keep `tools/prompt_map_audit.py` passing; it now checks referenced expression/composition/scene pools, item-template axes, and object-template metadata values for both string and object templates. ### Node / UI Path Owner: `__init__.py`, `node_builder.py`, `node_seed_resolution.py`, `node_camera.py`, `node_character.py`, `node_hardcore_position.py`, `node_formatter.py`, `node_insta.py`, `node_route_config.py`, `node_profile_filter.py`, `loop_nodes.py`, `web/*.js`. Keep here: - ComfyUI node input/output declarations; - widget behavior; - button actions; - dynamic input slots. - direct and config-driven builder node declarations in `node_builder.py`. - seed and resolution utility node declarations in `node_seed_resolution.py`. - camera utility node declarations in `node_camera.py`. - character pool, slot, and profile node declarations in `node_character.py`. - hardcore position pool/filter node declarations in `node_hardcore_position.py`. - caption/Krea2/SDXL formatter node declarations in `node_formatter.py`. - Insta/OF options and prompt-pair node declarations in `node_insta.py`. - route/category/location/composition/cast config node declarations in `node_route_config.py`. - profile/filter/ethnicity-list node declarations in `node_profile_filter.py`. Already isolated: - direct and config-driven prompt builder nodes live in `node_builder.py`, with registration maps imported by `__init__.py`. - seed axis salts/aliases, seed mode choices, lock builders, seed config parsing, row seed math, and deterministic axis RNG live in `seed_config.py`; seed/global-seed/seed-locker nodes live in `node_seed_resolution.py`, with registration maps imported by `__init__.py`. - SDXL/Krea2 resolution utility nodes live in `node_seed_resolution.py`, with registration maps imported by `__init__.py`. - camera/orbit/Qwen translator utility nodes live in `node_camera.py`, using `camera_config.py` for option lists and JSON builders, with registration maps imported by `__init__.py`. - hair, age/body/eyes/clothing pools, manual character details, character slots, and profile save/load nodes live in `node_character.py`, with registration maps imported by `__init__.py`. - hardcore position pool and action filter nodes live in `node_hardcore_position.py`, with registration maps imported by `__init__.py`. - caption naturalizer, Krea2 formatter, and SDXL formatter nodes live in `node_formatter.py`, with registration maps imported by `__init__.py`. - Insta/OF options and dual prompt-pair nodes live in `node_insta.py`, with registration maps imported by `__init__.py`. - category preset, location/composition pool, location theme, and cast config utility nodes live in `node_route_config.py`, with registration maps imported by `__init__.py`. - generation profile, advanced filter, and ethnicity list utility nodes live in `node_profile_filter.py`, with registration maps imported by `__init__.py`. - index-switch constants, index-base normalization, missing-input behavior, route-output selection, status text, and lazy-input selection live in `index_switch_policy.py`; `loop_nodes.py` keeps the ComfyUI node wrapper and accumulator/loop runtime logic. - node input tooltip inventory, node-specific tooltip overrides, dynamic input fallback tooltip rules, and tooltip injection live in `node_tooltips.py`; `__init__.py` only applies the installer to the assembled node registry. - profile-save and accumulator server payload handling lives in `server_routes.py`; `__init__.py` only wires those pure handlers to ComfyUI JSON responses, and `tools/prompt_smoke.py` covers the handlers without importing ComfyUI. Improve later: - split remaining large node classes into files by family; - keep node display names, return names, and docs in sync through the audit helper; - add more endpoint tests when new server routes are introduced. ## Path-Specific Improvements ### Prompt Builder Near-term: - Add final row hygiene already done through `prompt_hygiene.py`. - Add a metadata smoke checker for representative generated rows and static formatter fixtures through `tools/prompt_smoke.py`. - Normalize every row with one function before JSON serialization. Medium-term: - Extract category loading and role graph logic. - Convert keyword-heavy interaction filtering to template metadata. ### Insta/OF Pair Near-term: - Normalize pair metadata with one helper, including embedded row prompt, caption, negative, and side-specific metadata synchronization. - Confirm pair prompts, captions, and soft/hard rows carry the same sanitized scene/camera/clothing fields. - Keep same-room pair continuity synchronized in both assembled prompt text and `hardcore_row.scene_text`; `tools/prompt_smoke.py` covers this drift case. Medium-term: - Make pair camera and clothing phases explicit subfunctions. - Add smoke fixtures for same-cast, POV man, explicit nude, and different-camera modes. ### Krea2 Near-term: - Add final prose hygiene already done through `prompt_hygiene.py`. - Add smoke coverage through `tools/prompt_smoke.py` for metadata-driven Krea2 formatting across built-in rows, hardcore rows, same-cast pairs, and POV pairs. - Cover camera-scene preservation through `tools/prompt_smoke.py` for single rows, split soft/hard pair cameras, and POV camera-scene routing. - Cover config-node routing through `tools/prompt_smoke.py` for category, cast, generation profile, seed lock, camera, location theme, and composition config. - Cover close foreplay and POV penetration Krea routes so raw labels, invalid surface grammar, normal third-person camera text, and composition punctuation drift are caught. - Cover POV outercourse, oral, penetration, anal, and front/back double-contact Krea routes so selected position geometry stays synchronized with metadata. - Cover generated climax routes through Krea, SDXL, and natural caption outputs so source aftermath placement and formatter details cannot drift apart. - Cover generated interaction routes through Krea, SDXL, and natural caption outputs so source contact/guidance/presentation wording stays metadata-driven. - Cover generated fallback role routes through Krea, SDXL, and natural caption outputs so solo and same-sex paths do not remain untested edge behavior. Medium-term: - Dispatch action rewriting by action family. - Continue splitting remaining Krea semantic helpers into smaller modules. ### SDXL Near-term: - Add final tag hygiene already done through `prompt_hygiene.py`. - Add smoke tests for trigger preservation and duplicate tag removal through `tools/prompt_smoke.py`. Medium-term: - Make style/quality presets data-driven. ### Naturalizer Near-term: - Add final prose hygiene already done through `prompt_hygiene.py`. - Verify training captions keep trigger exactly once through `tools/prompt_smoke.py`. Medium-term: - Add caption profiles for training and browsing use cases. ### Camera / Scene Near-term: - Keep Qwen/orbit as camera source. - Keep scene-camera adapters scoped by location family. - Use the memory note in `/home/ethanfel/.codex/memories/scene-camera-system.md` when editing POV. - Keep `scene_camera_adapters.py` as the owner for location-aware camera prose; add new location families there one at a time. - Keep `row_camera.py` as the owner for inserting camera/scene directives into generated rows, including POV suppression of normal third-person camera text. Medium-term: - Build new adapters one location family at a time. ## Invariants To Preserve - Metadata is the preferred formatter input. - Prompt Builder should output structured rows even if raw prompt text is rough. - Krea should fix prose and semantic action readability, not category selection. - SDXL should produce tag-style output and preserve model triggers as requested. - Naturalizer should output training-friendly captions without changing the selected content. - Generic cleanup belongs in `prompt_hygiene.py`; semantic cleanup belongs in the owning route. ## Recommended Next Passes 1. Continue splitting remaining `__init__.py` node classes by family after behavior is covered by smoke checks. 2. Continue splitting the internals of `hardcore_role_graphs.py` by action family once generated edge cases are covered by smoke fixtures. 3. Add more route-level smoke fixtures for generated edge cases that are not covered by the current static Krea/SDXL/caption metadata fixtures.