# Prompt Pool Routing Map

This document maps the roads that can lead to prompt text. It intentionally does
not duplicate every prompt phrase in the JSON pools. The useful debugging target
is usually: which node path selected which pool, which seed axis controlled it,
and whether the final wording was still raw builder text or rewritten by a
formatter.

## Mental Model

There are three layers:

1. Generation layer: selects category, subcategory, item/action, character
   descriptors, scene, expression, pose, composition, and camera directives.
2. Pair/adapter layer: optionally builds a softcore/hardcore pair, applies
   continuity, POV handling, camera-aware scene text, and clothing state.
3. Formatter layer: rewrites a metadata row into Krea2 prose, SDXL tags, or a
   naturalized caption.

When a result is wrong, first identify which layer owns the bad text:

- Raw builder prompt already wrong: edit `prompt_builder.py` or the relevant
  `categories/*.json` pool/template.
- Raw builder prompt acceptable, Krea2 output wrong: inspect `krea_formatter.py`
  orchestration, then the owning Krea route/policy helper.
- Raw builder prompt acceptable, SDXL tags wrong: inspect `sdxl_formatter.py`
  orchestration, then `sdxl_tag_policy.py` and `sdxl_tag_routes.py`.
- Natural caption/training caption wrong: inspect `caption_naturalizer.py`
  orchestration, then `caption_text_policy.py`, `caption_policy.py`, and
  `caption_metadata_routes.py`.
- UI/preview/loop behavior wrong: edit `__init__.py`, node family modules such
  as `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`, or `node_profile_filter.py`,
  `loop_nodes.py`, or `web/*.js`.

## High-Level Routes

```mermaid
flowchart TD
  A[SxCP Prompt Builder] --> B[prompt_builder.build_prompt]
  C[SxCP Prompt Builder From Configs] --> D[parse config nodes]
  D --> B
  E[SxCP Insta/OF Prompt Pair] --> F[prompt_builder.build_insta_of_pair]
  F --> B
  B --> R[metadata row + prompt + negative + caption]
  F --> P[pair metadata: soft row + hard row]
  R --> K[SxCP Krea2 Formatter]
  R --> S[SxCP SDXL Formatter]
  R --> N[SxCP Caption Naturalizer]
  P --> K
  P --> S
  P --> N
```

The config nodes mostly emit JSON. The final builder nodes parse that JSON and
call the same core generation functions.

For Insta/OF pair formatting, the embedded `hardcore_row` is authoritative for
hardcore scene/composition text. Same-room continuity may copy the soft scene
into that row earlier, but formatter routes should not bypass later hard-row
cleanup such as clothing/body-access scene sanitization.

## Main Entry Points

| ComfyUI node | Python entry | What it owns |
| --- | --- | --- |
| `SxCP Prompt Builder` | `build_prompt` -> `builder_prompt_route.py` | Direct single prompt generation. Can use built-in categories or JSON categories. |
| `SxCP Prompt Builder From Configs` | `build_prompt_from_configs` -> `builder_config_route.py` -> `build_prompt` -> `builder_prompt_route.py` | Same generator, but inputs come from category/cast/profile/filter helper nodes. |
| `SxCP Insta/OF Prompt Pair` | `build_insta_of_pair` | Builds a softcore row and hardcore row with shared cast/continuity options. |
| `SxCP Scene Start` / `SxCP Scene Output` / `SxCP Scene Pair Output` | `node_scene.py` -> existing builder and pair routes | v2 structured `SXCP_SCENE` chain. Layers are split into cast, character, wardrobe, location, set dressing, blocking, action, performance, camera, composition, and lighting before compatibility rendering. |
| `SxCP Krea2 Formatter` | `format_krea2_prompt` -> `krea_format_route.py` | Converts metadata rows or pair metadata into Krea2-friendly prose. |
| `SxCP SDXL Formatter` | `format_sdxl_prompt` -> `sdxl_format_route.py` | Converts metadata rows or pair metadata into SDXL/tag style prompts. |
| `SxCP Caption Naturalizer` | `naturalize_caption` -> `caption_format_route.py` | Converts rows into more natural sentence captions. |

V2 scene-chain display nodes: `SxCP Scene Cast`, `SxCP Scene Character`,
`SxCP Scene Wardrobe`, `SxCP Scene Location`, `SxCP Scene Set Dressing`,
`SxCP Scene Blocking`, `SxCP Scene Action`, `SxCP Scene Performance`,
`SxCP Scene Camera`, `SxCP Scene Composition`, `SxCP Scene Lighting`,
`SxCP Scene Branch Pair`, `SxCP Softcore Branch Options`, and
`SxCP Hardcore Branch Options`.

V2 scene-chain side option nodes: `SxCP Scene Layer Seed Options`,
`SxCP Scene Cast Options`, `SxCP Scene Character Options`,
`SxCP Scene Wardrobe Options`, `SxCP Scene Location Layout Options`,
`SxCP Scene Set Dressing Options`, `SxCP Scene Blocking Options`,
`SxCP Scene Action Options`, `SxCP Scene Performance Options`,
`SxCP Scene Camera Options`, `SxCP Scene Composition Options`,
`SxCP Scene Lighting Options`, and `SxCP Scene Branch Options`. These nodes
feed typed option sockets into the main scene chain so the visible chain can
stay organized by movie-scene layer while deeper knobs remain adjacent.

Core helper ownership:

| Python module | What it owns |
| --- | --- |
| `category_library.py` | JSON category loading, subcategory normalization, named scene/expression/composition pool loading, cast compatibility filtering, exact subcategory lookup, and inheritance-based pool merging. |
| `builder_prompt_route.py` | Single-prompt builder orchestration, input normalization, seed-axis setup, built-in/custom row routing, legacy location/composition handling, camera application, and final prompt-row normalization. |
| `builder_config_route.py` | Config-driven prompt-builder request parsing, category/cast/profile/filter helper-node mapping, and direct `build_prompt` kwarg assembly. |
| `category_extensions.py` | JSON `pool_extensions`, legacy pool patching, built-in category choice lists, and category/subcategory UI choices. |
| `category_template_metadata.py` | Object-style and inherited item-template metadata extraction, action/position family normalization, position-key normalization, key merging, formatter-hint merging, and audit validation errors. |
| `row_item.py` | Row item selection, weighted item/pair choice, item-template axis filling, and oral/outercourse/anal axis compatibility filters. |
| `outercourse_action_policy.py` | Shared boobjob, testicle-sucking, penis-licking, handjob, and footjob action-kind classification used by row selection, role graphs, and Krea detail routing. |
| `row_category_route.py` | Row category/subcategory/item route resolution behind `CategoryItemRoute`, hardcore position-category filtering, cast-count adjustment, pose-vs-content seed-axis choice, item metadata collection, legacy dict compatibility, and pose-category item sanitizing. |
| `row_rendering.py` | Row prompt/caption text-field resolution, template selection, safe formatting, default prompt templates, configured-cast descriptor insertion, and POV directive insertion. |
| `row_role_graph.py` | Row role-graph route sequencing, including hardcore source graph construction, pose-category environment-anchor cleanup, and POV role-graph rewriting. |
| `row_assembly.py` | Final custom-row dictionary assembly behind `CustomRowAssemblyRequest`, render-context metadata population, prompt/caption rendering delegation, row-base indexing, cast/profile/slot metadata copying, and disabled-expression cleanup. |
| `row_route_metadata.py` | Row action/position route metadata resolution behind `ActionPositionRoute`, template metadata precedence, inferred position-key merging, legacy dict compatibility, and source action-family fallback. |
| `row_generation.py` | Built-in legacy row generation, auto-weighted/auto-full selection, row mode randomization, ratio clamps, and expression-intensity randomization. |
| `category_cast_config.py` | Category preset and cast preset schemas, category/cast config JSON builders, choice lists, and config parsers used by route nodes. |
| `cast_context.py` | Generation-time cast count phrases, configured-cast context metadata, character-slot label assignment, cast-summary wording, scene-kind labels, and couple count normalization. |
| `camera_config.py` | Camera option schema, direct/orbit/Qwen camera JSON builders, camera config parsing, plain camera directive text, and camera caption labels. |
| `character_appearance.py` | 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. |
| `character_config.py` | Character choice lists, descriptor detail/presence/slot-seed normalization, characteristic-list JSON builders/parsers, eye labels, hair config builders/parsers, and hair phrase helpers. |
| `character_profile.py` | 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. |
| `character_slot.py` | Character slot JSON construction, character-cast parsing, slot normalization, slot summary text, slot expression override policy, slot seed helpers, and slot figure/ethnicity normalization. |
| `filter_config.py` | Ethnicity/filter choices, advanced filter JSON, ethnicity-list JSON, filter parsing, and ethnicity normalization used by builder and character routes. |
| `generation_profile_config.py` | Generation profile presets, profile option overrides, trigger policy, expression/pose/clothing config normalization, and profile config parsing. |
| `seed_config.py` | Seed axis salts/aliases, seed mode choices, global/axis lock JSON builders, seed config parsing, row seed math, and deterministic axis RNG construction. |
| `subject_context.py` | Row subject-context routing for single, couple, configured-cast, group, and layout subjects, combining appearance policy, cast metadata, and generator subject pools. |
| `row_subject_route.py` | Row subject route orchestration, character slot/profile precedence, configured-cast POV labels, visible cast descriptor collection, and descriptor prompt cleanup. |
| `location_config.py` | Location/composition preset schemas, themed location packs, custom location/composition parsing, pool merge behavior, and location/composition config parsing. |
| `style_config.py` | Visual style preset schemas, style-pool merge behavior, positive style suffixes, and style negative-prompt merging. |
| `row_location.py` | Built-in row location/composition config application, deterministic scene/composition choice, source metadata, and legacy prompt/caption rewrites. |
| `row_expression.py` | Row expression cleanup, expression route resolution, expression intensity weighting, character-slot/cast expression override resolution, per-character expression selection, and action-aware character-expression sanitizing. |
| `row_pools.py` | Row scene/expression/pose/composition pool routing, category inheritance handling, runtime location/composition pool overrides, and generator fallback pools. |
| `row_prompt_axes.py` | Row scene/pose/expression/composition axis selection behind `PromptAxesRoute`, compatible-entry filtering, expression-disabled handling, per-character expression promotion, legacy dict compatibility, POV composition adaptation, and pose-category environment sanitizing. |
| `hardcore_position_config.py` | Hardcore position/action-filter choices, selected-position normalization, config JSON builders/parsers, focus-policy toggles, subcategory allow-list policy, position-key detection, and category/template/axis filtering. |
| `pair_options.py` | Insta/OF option schema/defaults, softcore category/outfit/pose pools, partner outfit pools, clothing-continuity labels, negatives, hardcore cast count policy, and hardcore detail-density directives. |
| `pair_builder.py` | Insta/OF pair route sequencing behind `InstaPairBuildRequest` and `InstaPairBuildDependencies`, including option/filter/seed/cast parsing handoff, soft/hard row, cast, camera, clothing, and final output adapter orchestration. |
| `pair_rows.py` | Insta/OF soft/hard row creation behind `InstaPairRowsRoute`, softcore expression override resolution, Woman A slot context application, soft outfit/pose overrides, POV row fields, and legacy dict compatibility. |
| `pair_cast.py` | Insta/OF descriptor prose, descriptor-entry assembly, shared descriptors, cast-label cleanup, same-cast softcore descriptor text, partner styling selection, cast-summary wording, platform/level labels, softcore cast presence text, and hard cast summary text. |
| `softcore_text_policy.py` | Shared softcore pair prose for solo/same-cast/POV cast presence, caption side setup wording, and creator-shot teaser style directives. |
| `pair_camera.py` | Insta/OF soft/hard camera route resolution behind `InstaPairCameraRoute`, same-as-softcore camera mode, camera-detail override, camera-aware composition mutation, POV camera suppression, synchronized row/root camera metadata, and legacy dict compatibility. |
| `pair_clothing.py` | Insta/OF clothing sentence formatting and hardcore clothing continuity behind `HardcorePairClothingRoute`, body-exposure scene cleanup, action-aware body-access flags, conflicting outfit-piece cleanup, configured/default visible-person clothing, final root clothing-state assembly, and legacy dict compatibility. |
| `pair_output.py` | Insta/OF final pair prompts, trigger preservation, negative prompts, captions, and root pair metadata assembly. |
| `krea_format_route.py` | Top-level Krea dispatch, option normalization, metadata-vs-text input selection, single-vs-pair branching, extra positive/negative merging, final prose hygiene, and output shape. |
| `hardcore_role_graphs.py` | Source role graph construction for hardcore configured-cast rows, including POV-aware interaction geometry, called through `row_role_graph.py` for row generation. |
| `hardcore_role_fallback.py` | Solo, same-sex, mixed group fallback, and support-partner role graph wording for configured casts. |
| `hardcore_role_interaction.py` | Foreplay, manual stimulation, body worship, clothing transition, dominant guidance, camera performance, aftercare, and group coordination role graph wording. |
| `hardcore_role_oral.py` | Oral-sex role graph wording for kneeling, face-sitting, sixty-nine, edge-supported, side-lying, chair, standing, and reclining oral geometry. |
| `hardcore_role_outercourse.py` | Outercourse role graph wording for boobjob, testicle-sucking, penis-licking, handjob, and footjob geometry, keyed by `outercourse_action_policy.py`. |
| `hardcore_role_penetration.py` | Penetrative-sex role graph wording for missionary, cowgirl, reverse-cowgirl, doggy, standing, side-lying, raised-edge, kneeling-straddle, and lotus geometry. |
| `hardcore_role_anal.py` | Anal and double-contact role graph wording for rear-entry, raised-edge, kneeling, side-lying, and front/back double-position geometry. |
| `hardcore_role_climax.py` | Climax and ejaculation aftermath role graph wording for face/body/ass, lap, open-thigh, side-lying, and group front/back placement. |
| `hardcore_action_metadata.py` | Source action-family and position-family metadata used by Krea2, SDXL, and caption routes. |
| `route_metadata.py` | Shared row-level route metadata readers for normalized action family, position family/keys, and formatter hints used by Krea2, SDXL, and caption routes. |
| `pov_policy.py` | Shared POV slot detection, POV label merging/filtering, builder POV directives, source role-graph viewer replacement, and shared POV composition cleanup used by builder and Krea2 routes. |
| `scene_camera_adapters.py` | Location-aware camera/scene prose for coworking, library, private creator, mirror/bedroom/studio/bathroom, and semi-public corridor/garage/archive/etc. profiles, metadata-first profile resolution, and camera-aware composition cleanup. |
| `row_camera.py` | Row-level camera insertion, contextual scene composition mutation, subject-kind detection, POV label fallback, and POV suppression of normal camera directives. |
| `krea_row_fields.py` | Shared Krea normal-row field extraction for item, scene, pose, expression, composition/source-composition, camera, and style used by normal and configured-cast routes. |
| `krea_cast.py` | Shared formatter cast descriptor parsing, cast labels, cast prose, natural cast descriptor text, and label replacement used by Krea2 and caption routes. |
| `prompt_hygiene.py` | Generic prompt, caption, and negative-prompt cleanup, including route-agnostic negative-prompt merge/dedupe. |
| `row_normalization.py` | Final prompt-row and pair metadata normalization: legacy built-in subject/count/scene/appearance/item/pose/expression metadata enrichment, trigger prepending, extra-positive append, negative merge/dedupe, caption-part joining, embedded soft/hard row output and side-metadata synchronization, and embedded row sanitation. |
| `formatter_detail.py` | Shared formatter detail-level choices, normalization, and concise/balanced/dense gates used by Krea2 and caption routes. |
| `formatter_input.py` | Shared formatter input parsing: text cleanup, metadata/source JSON detection, trigger-prefix stripping, shared prompt field-label inventory, fallback field-label stripping, `Avoid:` splitting, prompt-field extraction, and metadata row-value fallback. |
| `formatter_target.py` | Shared formatter target choices and normalization for `auto`, `single`, `softcore`, and `hardcore`, including pair-side selection and combined-caption inclusion policy. |
| `node_tooltips.py` | Node input tooltip inventory, node-specific overrides, dynamic-input fallback rules, and tooltip injection installer used by `__init__.py`. |
| `server_routes.py` | Pure payload handlers for profile-save and accumulator server endpoints, used by ComfyUI routes and smoke tests without importing ComfyUI. |
| `sdxl_presets.py` | SDXL formatter profiles, style presets, quality presets, default negative prompt, and metadata-family tag hints used by the SDXL formatter and node choice lists. |
| `sdxl_format_route.py` | Top-level SDXL dispatch, formatter profile application, target and nude-weight normalization, metadata-vs-text input selection, single-vs-pair branching, final prompt/negative output shape, and fallback routing. |
| `sdxl_tag_policy.py` | SDXL tag splitting, tag-key dedupe, count inference, character descriptor tags, item-axis tags, metadata-family/camera/explicit helper tags, and route dependency assembly used by `sdxl_formatter.py` and `sdxl_tag_routes.py`. |
| `caption_format_route.py` | Top-level caption dispatch, input-hint and target normalization, caption profile application, metadata-vs-text branching, trigger wrapping, final prose hygiene, and method/output shape. |
| `caption_policy.py` | Caption naturalizer policy data and helpers: caption profiles, style tails, item labels, metadata-family caption labels, detail/style-policy normalization, clothing cleanup, and composition cleanup. |
| `caption_text_policy.py` | Caption sentence helpers, trigger wrapping, formatter-hint append, item-axis detail prose, row-value fallback wrappers, cast text wrappers, single-caption front parsing, and metadata-route dependency assembly used by `caption_naturalizer.py` and `caption_metadata_routes.py`. |
| `item_axis_policy.py` | Shared `item_axis_values` flattening, placeholder filtering, preferred dict-value extraction, priority-ordered Krea action context text, and row-axis text extraction used by Krea2, SDXL, and caption routes. |

## Node IO Map

Use this when wiring or debugging a workflow. If the formatter can receive
`metadata_json`, prefer wiring metadata instead of only prompt text. Metadata is
what keeps cast, role graph, POV labels, camera config, and soft/hard pair state
recoverable.

| Node | Important inputs | Important outputs |
| --- | --- | --- |
| `SxCP Prompt Builder` | category, subcategory, seed, optional config nodes including location/composition/style | `prompt`, `negative_prompt`, `caption`, `metadata_json`, `category`, `subcategory` |
| `SxCP Prompt Builder From Configs` | category/cast/profile/filter/config node outputs including style_config | Same as `SxCP Prompt Builder` |
| `SxCP Insta/OF Prompt Pair` | options, seed_config, character_cast, location/composition/style/camera, hardcore_position_config | `softcore_prompt`, `hardcore_prompt`, both negatives, both captions, `shared_descriptor`, `metadata_json` |
| `SxCP Krea2 Formatter` | `source_text`, connectable `metadata_json`, target | `krea_prompt`, both pair prompts if pair metadata exists, negative outputs, method, `route_trace_json` |
| `SxCP SDXL Formatter` | `source_text`, connectable `metadata_json`, target, style/quality preset | `sdxl_prompt`, both pair prompts if pair metadata exists, negative outputs, method, `route_trace_json` |
| `SxCP Caption Naturalizer` | `source_text`, connectable `metadata_json`, target | `natural_caption`, method, `route_trace_json` |

## Practical Recipes

These recipes identify the intended road before editing prompt text.

| Request | Preferred node route | Critical settings | If wrong, inspect |
| --- | --- | --- | --- |
| Keep character/location but change only sexual pose | `Global Seed` or fixed seed config -> builder/pair | Keep `person_seed` and `scene_seed` fixed; change `pose_seed` and usually `role_seed`; for hardcore categories check `content_seed_axis` | `sexual_poses.json`, `hardcore_position_config`, `krea_actions.hardcore_action_sentence` |
| Generate a specific hardcore oral/blowjob scene | `Hardcore Position Pool` -> `Hardcore Action Filter` -> `Insta/OF Prompt Pair` or `Prompt Builder` | Use `focus=oral_only` or disable non-oral families; keep `allow_oral=true`; constrain position pool to kneeling/standing/oral variants when needed | `sexual_poses.json` oral subcategory/templates, `hardcore_position_config.apply_hardcore_position_config_to_subcategory`, `krea_actions.hardcore_action_sentence` |
| Generate POV oral or POV penetration | `Man Slot` with POV presence -> `character_cast` -> pair/builder -> Krea2 formatter | POV man must be in the cast; use metadata into Krea2; normal camera directive is suppressed by POV | `krea_pov_actions.py`, `krea_pov.py`, `krea_cast.cast_prose` omit-label handling |
| Generate porn-scene interaction beats | `Hardcore Position Pool` -> `Hardcore Action Filter` -> pair/builder | Use `focus=interaction_only` for kissing/body worship/transitions/guidance/camera/watching/aftercare, or `focus=manual_only` for fingering/clit/manual stimulation; constrain keys such as `camera_showing`, `wrist_pinning`, `fingering`, `aftercare` | `sexual_poses.json` interaction/manual subcategories, `_role_graph`, `krea_action_context.is_foreplay_text` / `krea_actions.hardcore_action_sentence` |
| Same woman, same room, softcore and hardcore outputs | `Character Slot/Profile` -> `Insta/OF Options` -> `Insta/OF Prompt Pair` | `continuity=same_creator_same_room`; set `softcore_cast` as needed; use pair metadata into formatter | `build_insta_of_pair`, `softcore_row`, `hardcore_row`, pair metadata fields |
| Same cast in softcore and hardcore | Character slot chain -> `Insta/OF Options` | `softcore_cast=same_as_hardcore`; configure partner slots/outfits if needed | `pair_cast.softcore_partner_styling`, character slot clothing, pair Krea branch |
| Change only outfit/clothing | Character clothing or category content route | Keep `person_seed`, `scene_seed`, `pose_seed`; change `content_seed`; slot `softcore_outfit` overrides Insta/OF outfit | `SxCP Character Clothing`, `pair_options.py`, category item templates |
| Force a custom location | `SxCP Location Pool` or `SxCP Location Theme` -> builder/pair | `combine_mode=replace` to force; `add` to mix with category scenes | `_scene_pool`, `row_location.apply_location_config_to_legacy_row`, camera scene adapter |
| Force a custom frame/composition | `SxCP Composition Pool` or `SxCP Location Theme` -> builder/pair | `combine_mode=replace` to force; `add` to mix | `_composition_pool`, `row_location.apply_composition_config_to_legacy_row`, Krea composition phrase |
| Force realistic/photo/comic style | `SxCP Style Pool` -> builder/pair/Scene Start | `combine_mode=replace` to override category style; `add` to append; choose realistic/photo/comic preset | `style_config.py`, `row_rendering.resolve_row_text_fields`, row `style` / `positive_suffix` metadata |
| Use Qwen/orbit camera geometry | Qwen/orbit node -> camera_config -> builder/pair | For pair, use `softcore_camera_config` and/or `hardcore_camera_config`; set mode from config in options | `_camera_config_with_mode`, `_camera_directive`, `_camera_scene_directive_for_context` |
| Use Krea2 for only hard prompt from a pair | Pair `metadata_json` -> Krea2 Formatter | `target=hardcore`, `input_hint=metadata_json` or auto with metadata connected | `_insta_pair_to_krea`, hard row fields |
| Convert builder output to SDXL tags | Builder/pair metadata -> SDXL Formatter | Use metadata input; set `target`; select style and quality preset | `sdxl_tag_routes.py`, `sdxl_tag_policy.py`, compatibility wrappers `_row_core_tags` / `_soft_tags` / `_hard_tags` |
| Convert pair metadata to one training caption side | Pair `metadata_json` -> Caption Naturalizer | `target=softcore` or `target=hardcore`; use `training_concise` or `training_dense` as needed | `caption_format_route.py`, `caption_metadata_routes.insta_of_pair_from_row_result` |
| Save/reuse character | Slot/profile nodes -> Profile Save/Load -> slot/builder | Save from the row/profile data you want, not a freshly randomized disconnected route | `character_profile.py`, `web/profile_buttons.js`, profile JSON |

## Seed Axes

Seed routing is centralized in `seed_config.py` around `SEED_AXIS_SALTS`,
`SEED_AXIS_ALIASES`, and `axis_rng`.

| Axis | Controls |
| --- | --- |
| `category` | Main category selection when random/auto. |
| `subcategory` | Subcategory selection. |
| `content` | Clothing item, category item, generated outfit, many softcore outfit choices. |
| `person` | Character appearance when not fully manual. |
| `scene` | Location/scene choice. |
| `pose` | Generic pose choice, and for pose-content categories, the hardcore position/action item. |
| `role` | Role graph / action choreography for sexual pose categories. Falls back through pose aliases. |
| `expression` | Expression choice and expression intensity randomization. |
| `composition` | Composition/framing choice. |

`SxCP Global Seed`, `SxCP Seed Control`, and `SxCP Seed Locker` all feed
`seed_config`. Values below zero mean the row's main seed still drives that
axis. Fixed axis seeds allow changing only one road, for example changing
`pose`/`role` while keeping person, scene, and category stable. `SxCP Seed
Control` keeps `seed_config` as its first output and also emits a `summary`
showing resolved per-axis values, including random-mode seeds whose widget value
may still be `-1`.

## Seed Playbook

The seed system has two levels: the main row seed and optional per-axis seeds.
If an axis seed is negative or absent, the main row seed plus row number drives
that axis. If an axis seed is fixed, that axis is reproducible even while other
axes change.

| Goal | Seed setup |
| --- | --- |
| Exact full regeneration | Keep main `seed`, `row_number`, `start_index`, and every connected config identical. |
| Same person, new pose | Fix `person_seed`; change `pose_seed` and usually `role_seed`. For hardcore pose categories, changing `content_seed` may also matter if the selected category uses content for pose items. |
| Same scene, new character | Fix `scene_seed`; change `person_seed`. |
| Same action, new framing | Fix `pose_seed`, `role_seed`, and `content_seed`; change `composition_seed` and/or camera config. |
| Same outfit, new pose | Fix `content_seed`; change `pose_seed`/`role_seed`. |
| Same soft/hard pair but different hardcore action | In pair mode, keep `person_seed`, `scene_seed`, `content_seed` if clothing must stay; change `pose_seed`/`role_seed`. |
| Debug expression only | Fix everything except `expression_seed` or expression intensity. |

Common trap: `row_number` participates in `seed_config.axis_rng`. If two
workflows have the same seeds but different `row_number`, they are not expected
to match.

Character `slot_seed` is more specific than the `person` axis. A fixed
`slot_seed` owns that slot's random age/body/appearance/hair choices, so
rerolling `person_seed` will not drift that character. Leave `slot_seed=-1`
when the slot should follow `person_seed` or the global seed like the fallback
cast generator.

Each generated row stores `generation_trace.seed_axes` in `metadata_json`.
Use it to verify whether an axis followed the main seed or a configured seed,
and to compare the exact per-axis RNG seed used for the row.

`tools/prompt_map_audit.py` includes a runtime metadata route check. It builds a
representative single row and Insta/OF pair, verifies embedded
`generation_trace` fields, and confirms Krea2, SDXL, and caption formatters
consume metadata JSON instead of silently falling back to raw prompt text. The
formatter route traces also expose selected row metadata such as selected pair
side, category, action/position family, scene profile, position keys, and POV
labels.
The same audit also statically rejects direct `row["prompt"]` reads in
formatter metadata modules outside the shared fallback helpers.

## Category Sources

There are two category systems.

| Source | Files/functions | Notes |
| --- | --- | --- |
| Built-in legacy generator | `generate_prompt_batches.py`, `row_generation.py` | Handles legacy `woman`, `man`, `couple`, `group_or_layout`, `auto_weighted`, and `auto_full`. |
| JSON category library | `categories/*.json`, `category_library.load_category_library`, `_build_custom_row` | Handles expandable categories such as casual clothes, erotic clothes, and hardcore sexual poses. |

JSON categories are the scalable system. Add new main categories or subcategories
there unless the behavior needs Python logic.

Exact JSON subcategory selection uses the full selector returned by
`category_library.exact_subcategory_selector`: `Category name / Subcategory
name`. The resolver parses that selector against the loaded category library
instead of blindly splitting the first slash separator, so custom category or
subcategory names may themselves contain `/` without drifting to a sibling
route.

## JSON Category Road

```mermaid
flowchart TD
  A[category/subcategory input] --> B[_find_subcategory]
  B --> C[item from subcategory.items]
  C --> D[row_item.compose_item]
  D --> E[item_templates + axes]
  B --> F[_scene_pool]
  B --> G[_expression_pool]
  B --> H[_pose_pool]
  B --> I[_composition_pool]
  E --> J[role graph / item text / axis values]
  F --> R[scene_text]
  G --> R
  H --> R
  I --> R
  J --> R[metadata row]
```

Important JSON keys:

- `categories`: main category definitions.
- `subcategories`: selectable subcategories inside a category.
- `items`: item/action entries selected by the content or pose axis.
- `item_templates`: templates with axis placeholders.
- `item_templates` entries may be strings or objects with `template` plus
  optional route metadata such as `action_family`, `action_type`,
  `position_family`, `family`, `position_key`, `position_keys`, and
  `formatter_hint`. Formatter hints may be a string/list for all routes or a
  map keyed by `krea`, `sdxl`, or `caption`; aliases such as `krea2` and
  `training_caption` are normalized by `category_template_metadata.py` and
  consumed only by the matching formatter route plus the shared `all` route.
- `item_template_metadata`: optional default route metadata on a category,
  subcategory, or item. String templates inherit it, and object templates can
  override it while formatter hints merge.
- For mixed hardcore subcategories, `action_family: default` keeps the explicit
  position family while allowing `row_route_metadata.py` to infer the semantic
  action family from the selected action/role text.
- `axes`: values used to fill `item_templates`.
- `scene_pool` / `scene_pools` or direct `scenes`: location road.
- `expression_pool` / `expression_pools` or direct `expressions`: expression road.
- `composition_pool` / `composition_pools` or direct `compositions`: framing road.
- `poses`: category-specific pose fallback.
- `prompt_template` / `caption_template`: final row prompt/caption assembly,
  selected and formatted by `row_rendering.py`.
- `inherit_scenes`, `inherit_expressions`, `inherit_compositions`: stop or allow
  inheritance from category/subcategory/item levels.
- `pool_extensions`: patch legacy pools from JSON through `category_extensions.py`.

Current category/pool files:

| File | Main ownership |
| --- | --- |
| `categories/default_categories.json` | Casual clothes, men/couple casual variants, normal JSON categories. |
| `categories/erotic_clothes.json` | Provocative/erotic clothing categories and their scene/expression/composition pools. |
| `categories/sexual_poses.json` | Hardcore sexual pose/action categories, role graphs, explicit scene/expression/composition pools. |
| `categories/location_pools.json` | Named scene pools and location pool extensions. |
| `categories/expression_composition_pools.json` | Named expression pools and composition pools. |

## Pool Ownership Matrix

This table is the first stop when the selected content is wrong.

| File / pool area | Owns | Selection axis | Formatter risk |
| --- | --- | --- | --- |
| `default_categories.json` woman casual subcategories | Casual outfit items, casual scenes, casual expressions, casual compositions | `category`, `subcategory`, `content`, `scene`, `expression`, `composition` | Low unless Krea/SDXL needs shorter clothing tags |
| `default_categories.json` men casual subcategories | Male casual outfit/items and men-specific casual pools | Same as above | Medium if men are part of a mixed cast and clothing detail is too strong |
| `default_categories.json` couple casual subcategories | Couple outfit/action-ish soft poses and couple pools | Same as above | Medium because labels and partner styling can duplicate pair mode |
| `erotic_clothes.json` | Provocative/erotic clothing categories and softcore creator scenes | `content`, `scene`, `expression`, `composition` | Medium because nude/implied-nude wording can conflict with clothes |
| `sexual_poses.json` foreplay/interaction/manual/oral/outercourse/penetration/etc. | Hardcore action and porn-scene interaction templates, role graphs, axis values, hardcore pool references | `pose` for pose-content route, also `role`; sometimes `content` aliases matter | High because Krea2 rewrites action and POV position text |
| `location_pools.json` | Reusable scene pools and legacy scene extensions | `scene` | Medium when a camera-aware adapter changes scene/composition wording |
| `expression_composition_pools.json` | Reusable expressions and framing/composition pools | `expression`, `composition` | Medium because formatter may label or suppress expressions |
| `pair_options.py` | Insta/OF option defaults, softcore level-to-category mapping, creator outfit/pose pools, partner outfit pools, negatives, hard cast count policy, and hardcore detail-density directives | Options node plus `content`/`pose` axes inside pair route | Medium because pair route pools must remain consistent with Krea/SDXL pair formatting |
| `generate_prompt_batches.py` legacy pools | Built-in generator clothing, pose, expression, scene, composition lists | Main row seed plus axis config through legacy adapter | Medium because legacy prompt format is field-label heavy |

When adding a new pool, choose JSON when the change is pure selectable wording.
Choose Python only when selection logic, compatibility filters, camera adaptation,
profile behavior, or formatter rewriting must change.

## Pool Resolution

### Scene / Location

Scene text is selected by `_scene_pool`.

Resolution order:

1. `SxCP Location Pool` / `SxCP Location Theme` with `replace` overrides the
   category scene pool.
2. Category/subcategory/item direct `scenes` and referenced scene pools are
   merged, unless inheritance is disabled.
3. `SxCP Location Pool` with `add` appends its entries.
4. Fallback is legacy `g.SCENES` or `g.GROUP_SCENES`.

Edit targets:

- Add reusable named locations: `categories/location_pools.json`.
- Add category-specific locations: the category JSON file.
- Add quick workflow-only locations: `SxCP Location Pool` custom locations.
  Plain text, `slug: text`, one-line JSON objects, and one-line JSON arrays are
  supported; JSON entries preserve metadata such as inline `camera_profile`.
- Add quick workflow-only compositions: `SxCP Composition Pool` custom
  compositions. Plain text and one-line JSON objects/arrays are supported.
- Add themed location packs: `THEMATIC_LOCATION_PRESETS` in `location_config.py`.

### Expression

Expression text is selected by `_expression_pool`, then filtered through
`row_expression.expression_entries_for_intensity`.

Resolution order:

1. Category/subcategory/item direct `expressions` and named expression pools.
2. Inheritance can stop at item or subcategory level.
3. Fallback is legacy `g.EXPRESSIONS`.
4. Character slots can override intensity per softcore/hardcore phase.
5. Expression can be disabled globally or per character slot.

Edit targets:

- General expression pools: `categories/expression_composition_pools.json`.
- Hardcore-specific expressions: usually `categories/sexual_poses.json` or named
  hardcore expression pools.
- Character-level expression settings: slot config and `row_expression.py`.
- Formatter expression wording: Krea route helpers, or `caption_text_policy.py`
  and `caption_metadata_routes.py` for natural captions.

### Pose / Action

Generic pose text is selected by `_pose_pool`. Hardcore sexual pose categories
are different: the sexual position/action is an item/template selected by the
content route, with `content_seed_axis` set to `pose` for pose-content
categories.

Edit targets:

- Normal pose pools: legacy `g.POSES`, `g.EVOCATIVE_POSES`, or JSON `poses`.
- Hardcore positions/actions: `categories/sexual_poses.json`.
- Hardcore interaction beats: `categories/sexual_poses.json` subcategories
  `foreplay_teasing`, `manual_stimulation`, `body_worship_touching`,
  `clothing_position_transitions`, `dominant_guidance`,
  `camera_performance`, `group_coordination`, and `aftercare_cleanup`.
- Position filtering UI/config and category/template/axis filter policy live in
  `hardcore_position_config.py`.
- Krea2 action rewrite orchestration: `krea_formatter.py`.
- Krea2 non-POV position anchors/arrangements: `krea_action_positions.py`.
- Krea2 non-climax item/detail cleanup: `krea_action_details.py`.
- Krea2 climax role/detail cleanup: `krea_action_climax.py`.
- Krea2 non-POV action-family routing: `krea_action_dispatch.py`.
- Krea2 non-POV action sentence assembly: `krea_actions.py`.
- Shared POV labels/composition cleanup: `pov_policy.py`.
- Krea2 POV camera support: `krea_pov.py`.
- Krea2 POV position rewrite: `krea_pov_actions.py`.

### Composition

Composition text is selected by `_composition_pool`.

Resolution order:

1. `SxCP Composition Pool` / `SxCP Location Theme` with `replace` overrides the
   category composition pool.
2. Category/subcategory/item direct `compositions` and named composition pools
   are merged.
3. `SxCP Composition Pool` with `add` appends entries.
4. Fallback is legacy `g.COMPOSITIONS` or `g.GROUP_COMPOSITIONS`.

Edit targets:

- General composition pools: `categories/expression_composition_pools.json`.
- Category-specific composition pools: relevant category JSON.
- Workflow overrides: `SxCP Composition Pool`.
- Camera-aware composition replacements: `_coworking_composition_prompt`.

## Character Route

```mermaid
flowchart TD
  A[Woman/Man/Character Slot chain] --> B[character_cast JSON]
  C[Profile Load] --> D[character_profile JSON]
  E[Manual Details / Hair / Body / Age / Eyes / Clothing nodes] --> A
  B --> F[_parse_character_cast]
  F --> G[_character_slot_label_map]
  G --> H[_context_from_character_slot]
  H --> R[cast descriptors + slot clothing + expression settings]
  D --> I[_apply_character_profile_to_context]
  I --> R
```

Important behavior:

- Slot chaining labels the closest slot to the final node as `A` for that gender,
  then upstream slots as `B`, `C`, and so on.
- `character_cast` is for multi-character configured casts. `character_profile`
  is for reusable primary-character details.
- `SxCP Character Manual Details` and characteristic pool nodes can feed slots.
- `SxCP Character Clothing` can feed `softcore_outfit` and `hardcore_clothing`.
- A man slot can be marked as POV. POV men are omitted from visible cast
  descriptors, and camera wording is adapted to first-person view.

Edit targets:

- Subject routing: `subject_context.py`; character slot JSON/parsing/summary:
  `character_slot.py`; generation-time appearance field resolution:
  `character_appearance.py`; character-slot label assignment:
  `cast_context.character_slot_label_map`; pair cast descriptor entry assembly:
  `pair_cast.cast_descriptor_entries`.
- Profile save/load: `SxCPCharacterProfileSave`,
  `SxCPCharacterProfileLoad`, profile policy in `character_profile.py`, and
  `web/profile_buttons.js`.
- Hair/body/ethnicity list behavior: characteristic config builders in
  `character_config.py` and ethnicity filters in `filter_config.py`.

## Insta/OF Pair Route

```mermaid
flowchart TD
  O[SxCP Insta/OF Options] --> P[prompt_builder wrapper]
  C[character_cast] --> P
  S[seed_config] --> P
  L[location_config] --> P
  M[composition_config] --> P
  H[hardcore_position_config] --> P
  P --> Q[pair_builder.py]
  Q --> R[pair_rows.py]
  R --> A[soft_row via build_prompt]
  R --> B[hard_row via build_prompt]
  A --> D[pair_cast.py]
  B --> D
  D --> X[pair metadata]
  B --> Y[pair_camera.py + pair_clothing.py]
  Y --> X
  X --> Z[pair_output.py]
  Z --> K[Krea2/SDXL/Naturalizer]
```

Softcore row:

- Category comes from `pair_options.INSTA_OF_SOFTCORE_SUBCATEGORY_BY_LEVEL`.
- Outfit comes from character slot `softcore_outfit` if present, otherwise
  `pair_options.INSTA_OF_SOFTCORE_OUTFITS`.
- Soft pose comes from `pair_options.INSTA_OF_SOFTCORE_POSES`.
- Partner styling is resolved through `pair_cast.softcore_partner_styling` when
  softcore cast is `same_as_hardcore`.

Hardcore row:

- Category is always `Hardcore sexual poses`.
- Cast count comes from `SxCP Insta/OF Options`.
- Position/action/interaction can be constrained by `SxCP Hardcore Position
  Pool` and `SxCP Hardcore Action Filter`.
- Clothing comes from character slot hardcore clothing first, then fallback
  `hardcore_clothing_continuity`.
- Men receive default hardcore clothing if visible and not configured.
- POV labels alter action, camera, composition, and visible cast descriptors.

Continuity:

- `same_creator_same_room` reuses the softcore scene for hardcore.
- `same_creator_new_scene` lets hardcore use its own scene.
- Shared cast descriptors are stored in pair metadata and consumed by formatters.

## Metadata Field Dictionary

The builder outputs JSON metadata because downstream formatters need more than
plain prompt text. When debugging, inspect these fields before editing pools.

### Normal Row Metadata

| Field | Owner | Consumed by | Meaning |
| --- | --- | --- | --- |
| `source` | `build_prompt` / row builder | All formatters | Usually `json_category` or `built_in_generator`; tells which route created the row. |
| `generation_trace` | `builder_prompt_route.build_prompt_result` | Debug | Compact generation route trace containing builder branch, input/resolved category, row seed, per-axis seed sources/RNG seeds, effective clothing/pose/figure choices, expression route, and content seed axis. |
| `main_category`, `subcategory` | `row_category_route.select_category_item_route` | All formatters and debug | Human-readable selected category route. |
| `category_slug`, `subcategory_slug` | `row_category_route.select_category_item_route` | Debug/filtering | Stable-ish machine labels for selected category route. |
| `content_seed_axis` | `row_category_route.select_category_item_route` | Debug | Shows whether the item/action was driven by `content` or `pose`. Critical for hardcore pose categories. |
| `item` | `row_category_route.select_category_item_route` or Insta override | Krea/SDXL/Naturalizer | Clothing item, category item, or sexual scene/action text. |
| `item_axis_values` | `row_category_route.select_category_item_route` | Krea hardcore rewrite, SDXL tags, natural captions | Filled template axes such as position/action/detail values. Shared flattening lives in `item_axis_policy.py`. |
| `item_template_metadata` | `row_category_route.select_category_item_route` | Debug, Krea/SDXL/Naturalizer route metadata | Metadata inherited from category/subcategory/item `item_template_metadata` plus selected object-template metadata; used to prefer explicit action/position families and keys before inference. |
| `formatter_hints` | `row_category_route.select_category_item_route` | Krea/SDXL/Naturalizer route specialization, debug | Normalized route-specific hints inherited from template metadata, keyed by `all`, `krea`, `sdxl`, or `caption`; each formatter consumes `all` plus its own route only. |
| `action_family` | `row_route_metadata.resolve_action_position_route` | Krea hardcore rewrite, SDXL tags, natural captions, debug | Source-aware formatter semantic family such as `foreplay`, `outercourse`, `oral`, `penetration`, `toy_double`, or `climax`. |
| `position_family` | `row_route_metadata.resolve_action_position_route` | Debug/filtering | Source/UI hardcore family selected by template metadata or subcategory, such as `manual`, `interaction`, `oral`, `anal`, or `climax`. |
| `position_key`, `position_keys` | `row_route_metadata.resolve_action_position_route` | Debug/future filters | Concrete position tokens from object-template metadata and inferred axes/role text, such as `kneeling`, `doggy`, `boobjob`, or `open_thighs`. |
| `custom_item`, `item_label` | Category/pair route | Formatters and debug | Label/name for item route. |
| `role_graph` | `_role_graph`, POV adapter | Krea/Naturalizer | Choreography/action relationship text after POV adaptation. |
| `source_role_graph` | `_role_graph` before POV rewrite | Krea hardcore rewrite | Raw action graph used to infer position and contact. |
| `scene_text` | `row_prompt_axes.resolve_prompt_axes` | All formatters | Final location text. |
| `scene_entry` | `row_prompt_axes.resolve_prompt_axes` / `row_location` | Debug/future route rules | Structured selected scene entry, preserving slug/prompt plus theme metadata when available. |
| `location_theme`, `scene_theme` | `location_config.py`, selected scene entry | Debug/camera route rules | Active theme on the location config and theme of the selected scene. This makes theme-driven behavior inspectable instead of only string-inferred. |
| `source_scene_text` | location/body-exposure/camera adapters | Debug/continuity | Previous scene text before an override. |
| `location_config` | Location config parser | Debug | Active location pool config, if connected. |
| `pose` | `row_prompt_axes.resolve_prompt_axes` | Formatters | Generic pose text. Less important for hardcore action categories than `item`/`role_graph`. |
| `expression` | `row_prompt_axes.resolve_prompt_axes` | All formatters | Final expression text unless disabled. |
| `shared_expression` | `row_prompt_axes.resolve_prompt_axes` | Debug | Expression before character-specific expansion. |
| `character_expression_text` | `row_prompt_axes.resolve_prompt_axes` | Krea/Naturalizer | Per-character expression clauses. |
| `expression_enabled`, `expression_disabled` | Builder/slot override | All formatters | Hard gate for whether expression text should appear. |
| `expression_intensity_source` | Builder/slot override | Debug | Explains whether intensity came from input, random, slot, or disabled state. |
| `composition` | `row_prompt_axes.resolve_prompt_axes` | All formatters | Final framing phrase. |
| `composition_entry`, `composition_theme` | `row_prompt_axes.resolve_prompt_axes` / `row_location` | Debug/future route rules | Structured selected composition entry and active composition theme. |
| `source_composition` | `row_prompt_axes.resolve_prompt_axes` | Krea hardcore rewrite | Previous/raw composition, often better for action inference. |
| `composition_config` | Composition config parser | Debug | Active composition pool config, if connected. |
| `camera_config` | Camera nodes/parser | Krea/SDXL/debug | Structured camera settings. |
| `camera_directive` | `_camera_directive` | Krea/Naturalizer/prompt text | Human camera sentence. Suppressed for POV. |
| `camera_scene_directive` | scene-camera adapter | Krea/Naturalizer/prompt text | Location-aware camera layout sentence. |
| `scene_camera_profile`, `scene_camera_profile_key` | `row_camera.apply_camera_config` | Debug/camera route rules | Structured camera profile selected for the current scene, e.g. `classical_library` or `coworking_lounge`. |
| `subject_type`, `subject_phrase` | `row_subject_route.resolve_subject_route` | Formatters | Single/couple/group/configured cast route. |
| `women_count`, `men_count`, `person_count` | `row_subject_route.resolve_subject_route` | Pair/formatters/debug | Effective cast counts. |
| `cast_descriptors`, `cast_descriptor_text` | `row_subject_route.resolve_subject_route` | Krea/SDXL/Naturalizer | Visible cast descriptors. |
| `character_cast_slots` | `row_subject_route.resolve_subject_route` | POV/camera/formatters | Raw configured slots. |
| `character_slot_status`, `character_profile_status` | `row_subject_route.resolve_subject_route` | Debug | Explains whether slot/profile was applied or skipped. |
| `pov_character_labels` | `row_subject_route.resolve_subject_route` | Krea/prompt/camera | Labels omitted from visible cast and rewritten as first-person POV. |
| `hardcore_position_config` | Hardcore position/filter nodes | Debug | Active hardcore family/position/action/interaction constraints, including `interaction_only` and `manual_only`. |
| `negative_prompt` | Category/pair/default negative route | Formatter output | Base negative text before formatter extras. |
| `trigger` | Builder input | Formatter/fallback/debug | Active trigger after fallback to default. |

### Insta/OF Pair Metadata

| Field | Owner | Consumed by | Meaning |
| --- | --- | --- | --- |
| `mode` | `build_insta_of_pair` | Formatters | `Insta/OF` selects pair formatter branches. |
| `options` | `SxCP Insta/OF Options` | Formatters/debug | Soft/hard level, cast mode, continuity, camera modes, expression settings. |
| `shared_descriptor` | `pair_cast.py` | Pair formatters | Primary creator descriptor. |
| `shared_cast_descriptors` | `pair_cast.py` | Pair formatters | Full cast descriptor list. |
| `softcore_row`, `hardcore_row` | Pair route | Pair formatters | Full normal metadata rows for each side; their prompt, caption, negative, shared cast descriptors, and side-specific metadata fields are synchronized to the final pair outputs/root fields during pair normalization. |
| `softcore_prompt`, `hardcore_prompt` | `pair_output.py` | Direct output/fallback | Raw pair prompts before formatter rewrite. |
| `softcore_negative_prompt`, `hardcore_negative_prompt` | `pair_output.py` | Formatter negatives | Separate negatives for each side. |
| `softcore_partner_styling` | `pair_cast.py` | Krea/SDXL pair branch | Partner softcore clothing and pose when same-cast softcore is enabled. |
| `character_hardcore_clothing` | Character slots | Krea pair branch | Explicit per-character hardcore clothing state. |
| `default_man_hardcore_clothing` | Pair fallback | Krea pair branch | Auto clothing for visible men without configured clothing. |
| `hardcore_clothing_state` | Pair clothing continuity | Krea/SDXL pair branch | Final hard clothing/body exposure sentence before Krea cleanup. |
| `hardcore_detail_density` | Insta/OF options via `pair_options.py` | Krea hardcore action rewrite | Controls compact/balanced/dense action detail directives. |
| `softcore_camera_config`, `hardcore_camera_config` | Pair camera route | Krea/SDXL pair branch | Separate camera configs after option mode resolution. |
| `softcore_camera_directive`, `hardcore_camera_directive` | Pair camera route | Krea pair branch | Separate plain camera sentences, suppressed for POV. |
| `softcore_camera_scene_directive`, `hardcore_camera_scene_directive` | Scene-camera adapter | Krea/Naturalizer pair branch | Separate location-aware camera layout text. |

## Hardcore Position Route

`SxCP Hardcore Position Pool` and `SxCP Hardcore Action Filter` both emit
`hardcore_position_config`.

```mermaid
flowchart TD
  A[Hardcore Position Pool] --> C[hardcore_position_config]
  B[Hardcore Action Filter] --> C
  C --> D[hardcore_position_config.filter_hardcore_categories_for_position]
  C --> E[hardcore_position_config.apply_hardcore_position_config_to_subcategory]
  E --> F[item_templates / axes in sexual_poses.json]
  F --> G[role_graph + item + axis_values]
  G --> H[Krea2 action sentence and POV rewrite]
```

What each part owns:

- `sexual_poses.json`: available positions, families, action templates, role
  graph templates, interaction templates, and action-specific pool references.
- `prompt_builder.py`: filters which templates/axes remain available.
- `hardcore_role_graphs.py` and action-family helper modules: turn selected
  item axes into source role graphs before formatter-specific rewrites.
- `krea_formatter.py`: orchestrates the selected action rewrite into
  model-readable prose.
- `krea_action_positions.py`: resolves non-POV pose anchors, body-arrangement
  text, duplicate arrangement checks, and action-position phrases.
- `krea_action_details.py`: normalizes non-climax item/detail text and dedupes
  foreplay, outercourse, oral, penetration, toy/double-contact, and anchor
  details.
- `krea_action_climax.py`: rewrites climax role graphs and dedupes aftermath
  detail/view clauses.
- `krea_action_dispatch.py`: normalizes non-POV role graphs, classifies action
  families, and applies the matching detail cleanup.
- `krea_actions.py`: assembles the final non-POV hardcore action sentence.
- `krea_pov_actions.py`: rewrites POV variants with first-person geometry.

Current broad hardcore families:

| Family / focus | Subcategories |
| --- | --- |
| `penetrative` / `penetration_only` | `penetrative_sex` |
| `foreplay` / `foreplay_only` | `foreplay_teasing` |
| `interaction` / `interaction_only` | `foreplay_teasing`, `body_worship_touching`, `clothing_position_transitions`, `dominant_guidance`, `camera_performance`, `group_coordination`, `aftercare_cleanup` |
| `manual` / `manual_only` | `manual_stimulation` |
| `oral` / `oral_only` | `oral_sex` |
| `outercourse` / `outercourse_only` | `outercourse_sex`, `manual_stimulation` |
| `anal` / `anal_only` | `anal_double_penetration` |
| `climax` / `climax_only` | `cumshot_climax` |
| `threesome` / `threesome_only` | `threesomes` |
| `group` / `group_only` | `group_sex_orgy` |

The action filter also has independent gates for toys, double-contact,
penetration, foreplay, interaction, manual stimulation, oral, outercourse, anal,
and climax. Keep `allow_interaction=true` when using the broader interaction
family; keep `allow_manual=true` when manual stimulation should remain possible.
`allow_anal=false` blocks anal-sex wording, not ordinary ass-touching interaction
phrases such as ass grabbing or body worship.

Interaction selector keys include kissing/caressing/undressing, body worship,
nipple play, ass grabbing, thigh kissing, hair holding, wrist pinning, dirty
talk, position transitions, guided positioning, camera presentation, watching,
aftercare, cleanup, fingering, clit rubbing, and mutual masturbation. These keys
are still routed through `hardcore_position_config.positions`, so they are
controlled by the same `pose`/`role` debug path as other hardcore action keys.

If one action keeps recurring, inspect:

1. The enabled position family/action flags.
2. `weight` values in `sexual_poses.json`.
3. Whether the selected subcategory has only a small compatible set after
   filtering.
4. Whether the formatter collapses several raw actions into the same final
   wording.

## Camera And Scene Route

Camera config nodes:

- `SxCP Camera Control`: direct camera settings.
- `SxCP Camera Orbit Control`: orbit-like camera settings.
- `SxCP Qwen Camera Translator`: converts qwen multi-angle info into
  `camera_config`.

Camera handling:

1. Camera nodes emit `camera_config` through `camera_config.py`.
2. `build_prompt` calls `_apply_camera_config`.
3. `camera_config.camera_directive` creates a plain camera sentence unless
   disabled/off.
4. `_camera_scene_directive_for_context` can add location-aware camera text.
5. POV rows suppress the normal camera directive and use first-person camera
   wording instead.

Current camera-aware scene adapter:

- Scene profiles live in `scene_camera_adapters.SCENE_CAMERA_PROFILES`.
- Profile resolution is metadata-first: explicit `scene_camera_profile_key`,
  selected `scene_entry` profile keys, and theme metadata are preferred before
  text matching.
- Selected scene entries can provide inline `camera_profile` or
  `scene_camera_profile` objects with `key`, `family`, `layout_label`, `place`,
  `foreground`, `midground`, `background`, `detail_label`, and optional
  per-subject `composition` text.
- Coworking/business-cafe/office scenes, classical library/book-stack scenes,
  private creator/mirror/bedroom/studio/bathroom scenes, and semi-public
  repeating-structure scenes such as hotel corridors, parking garages,
  archives, laundromats, station lockers, backstage halls, wine cellars,
  nightclub back halls, and restaurant booths are detected by
  `scene_camera_profile`. Known JSON scene slugs route through
  `SCENE_SLUG_PROFILE_KEYS` before text matching, which prevents broad terms
  like cafe, mirror, tiled walls, or bookshelves from hijacking unrelated
  locations.
- Location themes preserve `theme` on configs and selected scene entries, and
  rows expose `location_theme`, `scene_theme`, `composition_theme`, and
  `scene_camera_profile_key` for debugging and future route rules.
- Direction, distance, and elevation details come from profile-aware helpers
  such as `scene_direction_detail`, `scene_distance_detail`, and
  `scene_elevation_detail`.
- Composition cleanup for mismatched outfit-check, mirror, bag, or shoes
  wording happens in `contextual_composition_prompt`; compatibility wrappers
  keep the old coworking function names available.

Important POV rule:

- In POV rows, location anchors must stay behind, beside, or at the frame edges.
  The foreground belongs to the POV body/hands and visible partner/action.
- Profile `foreground` anchors such as desk edges, counters, shelves, doors, or
  pillars are normal non-POV placement aids. POV camera text should not reuse
  them as viewer-side objects; it should reserve the lower foreground for POV
  body or hand cues.

## Formatter Routes

Formatter metadata input is normalized by `formatter_input.py`. Pair routing is
structural: metadata with both a softcore side and a hardcore side
(`softcore_row`/`hardcore_row` or root soft/hard prompt/caption fields) is
treated as pair metadata even if the UI `mode` label is absent. Krea2, SDXL, and
caption routes share this detection to avoid hidden drift between formatter
paths. Formatter nodes expose `route_trace_json` with the formatter name,
branch, method, normalized target/input hint, selected pair side when relevant,
and route-specific controls.

### Krea2

`format_krea2_prompt` chooses between three roads:

- Pair metadata: `krea_pair_formatter.format_insta_pair_result` through the
  `_insta_pair_to_krea` compatibility wrapper.
- Normal configured-cast metadata row:
  `krea_configured_cast_formatter.format_configured_cast_result` through the
  `_normal_row_to_krea` compatibility wrapper.
- Other normal metadata rows:
  `krea_normal_formatter.format_normal_row_result` through the
  `_normal_row_to_krea` compatibility wrapper.
- Plain text fallback: `_fallback_text_to_krea`.

Key Krea2 ownership:

- Cast descriptor naturalization: `krea_cast.cast_prose`,
  `krea_cast.natural_label_text`.
- Shared action-family metadata: `hardcore_action_metadata.py`.
- Action context and family predicates: `krea_action_context.py`.
- Non-POV pose anchors and arrangements: `krea_action_positions.py`.
- Non-climax item/detail cleanup: `krea_action_details.py`.
- Climax role/detail cleanup: `krea_action_climax.py`.
- Non-POV action-family routing: `krea_action_dispatch.py`.
- Non-POV hardcore action sentence: `krea_actions.hardcore_action_sentence`.
- Insta/OF pair soft/hard Krea prose assembly:
  `krea_pair_formatter.format_insta_pair_result`.
- Normal configured-cast Krea prose assembly:
  `krea_configured_cast_formatter.format_configured_cast_result`.
- Normal single/couple/generic Krea prose assembly:
  `krea_normal_formatter.format_normal_row_result`.
- Shared POV labels/filtering/composition cleanup: `pov_policy.py`.
- Krea POV camera support: `krea_pov.py`.
- Detail clause splitting and density limiting: `krea_detail.py`.
- POV hardcore sentence: `krea_pov_actions.pov_action_phrase`.
- Clothing state cleanup: `krea_clothing.natural_clothing_state`.
- Camera scene preservation: `_camera_scene_phrase`.

Krea2 field consumption:

| Branch | Reads most from | Key functions |
| --- | --- | --- |
| Normal single/couple/generic row | `subject_type`, `item`, `pose`, `scene_text`, `expression`, `composition`, `camera_*`, style fields | `krea_normal_formatter.format_normal_row_result` |
| Normal configured cast/hardcore row | `cast_descriptor_text` only for cast descriptors, `women_count`, `men_count`, `source_role_graph`, `role_graph`, `item`, `item_axis_values`, `source_composition`, `pov_character_labels` | `krea_configured_cast_formatter.format_configured_cast_result`, `krea_actions.hardcore_action_sentence`, `krea_pov_actions.pov_action_phrase` |
| Insta/OF pair softcore | `shared_descriptor`, `softcore_row`, `softcore_partner_styling`, options, soft camera fields | `krea_pair_formatter.format_insta_pair_result` |
| Insta/OF pair hardcore | `hardcore_row`, `shared_cast_descriptors`, `hardcore_clothing_state`, `hardcore_detail_density`, hard camera fields, POV labels | `krea_pair_formatter.format_insta_pair_result`, `krea_actions.hardcore_action_sentence`, `krea_pov_actions.pov_action_phrase`, `krea_clothing.natural_clothing_state` |
| Plain text fallback | `source_text` only, including raw prompt labels such as `Scene:` / `Pose:` | `_fallback_text_to_krea` |

If metadata is connected and `method` says `text(fallback)`, the formatter did
not parse metadata. That is a wiring/input-hint issue, not a prompt pool issue.

### SDXL

`format_sdxl_prompt` chooses between:

- Pair metadata: `sdxl_tag_routes.soft_tags_result` and
  `sdxl_tag_routes.hard_tags_result` through compatibility wrappers.
- Normal metadata row: `sdxl_tag_routes.row_core_tags_result` through the
  `_row_core_tags` compatibility wrapper.
- Tag mechanics: `sdxl_tag_policy.py` supplies splitting, dedupe, count,
  character, metadata-family, camera, and explicit helper tags to the route
  layer.
- Plain text fallback: `_fallback_text_to_sdxl`.

Use this route for style triggers, weighted tag style, nude weighting, formatter
profiles, and Pony / SDXL quality/style presets.

SDXL field consumption:

| Branch | Reads most from | Key functions |
| --- | --- | --- |
| Normal metadata | `cast_descriptor_text` or structured age/body/skin/hair/eyes, `action_family`, `position_family`, `position_keys`, `item_axis_values`, item, role graph, clothing/body exposure state, scene, camera config/directive | `sdxl_tag_routes.row_core_tags_result`, `sdxl_tag_policy.normal_character_tags`, `sdxl_tag_policy.metadata_family_tags`, `sdxl_tag_policy.axis_value_tags`, `sdxl_tag_policy.camera_tags` |
| Pair softcore | `softcore_row`, pair partner styling, root soft camera config | `sdxl_tag_routes.soft_tags_result` |
| Pair hardcore | `hardcore_row`, `action_family`, `position_family`, `position_keys`, role graph, item, `hardcore_clothing_state`, expression/composition, hard camera fields | `sdxl_tag_routes.hard_tags_result`, `sdxl_tag_policy.metadata_family_tags` |
| Text fallback | `source_text`, preserve-trigger setting, shared field-label stripping, prompt labels such as `Characters:` | `_fallback_text_to_sdxl` |

SDXL is the right place for model trigger handling, tag ordering, weight syntax,
quality/style preset changes, and nude-weight defaults. Do not solve those in
JSON category pools unless the raw builder text is also wrong.

### Naturalizer

`naturalize_caption` chooses metadata-specific renderers such as
`caption_metadata_routes.configured_cast_from_row_result`,
`caption_metadata_routes.couple_from_row_result`, and the other metadata route
renderers through compatibility wrappers.

Use this route when the row metadata is correct but the sentence-style caption is
too mechanical or unsuitable for training captions.

Naturalizer field consumption:

| Branch | Reads most from | Key functions |
| --- | --- | --- |
| Normal single/couple/group | subject fields, age/body, item, scene, expression, composition, camera scene | `caption_metadata_routes.single_from_row_result`, `caption_metadata_routes.couple_from_row_result`, `caption_metadata_routes.group_or_layout_from_row_result` |
| Configured cast/hardcore | `cast_descriptor_text`, `action_family`, `position_family`, `role_graph`, `item`, `item_axis_values`, `scene_text`, expression, composition | `caption_metadata_routes.configured_cast_from_row_result`, `caption_text_policy.metadata_action_label`, `caption_text_policy.item_axis_detail_text` |
| Insta/OF pair | `softcore_row`, `hardcore_row`, pair options and continuity, target | `caption_metadata_routes.insta_of_pair_from_row_result` |
| Text fallback | `caption` or `prompt` text | `caption_naturalizer._text_to_prose`, with sentence helpers delegated to `caption_text_policy.py` |

### Final Text Hygiene

`prompt_hygiene.py` owns route-agnostic final cleanup. It is intentionally
small: whitespace, punctuation, empty field labels, adjacent duplicate
sentences, repeated trigger prefixes, duplicate comma-list items, and dangling
connectors.

It is called from:

- `prompt_builder.build_prompt`
- `prompt_builder.build_insta_of_pair`
- `krea_formatter.format_krea2_prompt`
- `sdxl_formatter.format_sdxl_prompt`
- `caption_naturalizer.naturalize_caption`

Do not put semantic fixes in `prompt_hygiene.py`. Sexual action readability,
POV geometry, clothing state, Krea prose, SDXL weighting, and training-caption
policy still belong to their route-specific owner.

## Utility / Workflow Nodes

These do not own prompt pool wording, but they affect execution and review:

| Node family | Files | Purpose |
| --- | --- | --- |
| Loop nodes | `loop_nodes.py`, `web/loop_slots.js` | While/for loop execution and carry values. Includes `SxCP While Loop Start`, `SxCP While Loop End`, `SxCP For Loop Start`, `SxCP For Loop End`, `SxCP Loop Int Add`, `SxCP Loop Less Than`, and `SxCP Loop Less Than Or Equal`. |
| Index switch | `loop_nodes.py`, `index_switch_policy.py`, `web/index_switch_slots.js` | `SxCP Index Switch`: multi-input to selected output, and selected input to multi-output routing. Pure index-base, missing-input, route-output, status, and lazy-input policy lives in `index_switch_policy.py`. |
| Accumulator | `loop_nodes.py`, `web/accumulator_preview.js` | Stores generated values/images during workflow execution and previews/reorders/deletes them. |
| Persistent text preview | `loop_nodes.py`, `web/preview_any_text.js` | Stores any value as text and keeps it after workflow reload. |
| Builder node wrappers | `node_builder.py`, imported by `__init__.py` | Direct prompt builder and config-driven prompt builder ComfyUI declarations. |
| Seed and resolution utility nodes | `node_seed_resolution.py`, imported by `__init__.py` | UI wrappers for global/per-axis seed configs via `seed_config.py`, plus SDXL/Krea width/height helpers. |
| Camera utility nodes | `node_camera.py`, imported by `__init__.py` | UI wrappers for direct camera config, orbit-to-camera config, and Qwen MultiAngle camera translation via `camera_config.py`. |
| Character utility nodes | `node_character.py`, imported by `__init__.py` | Hair, age/body/eyes/clothing pools, manual details, character slots, and profile save/load nodes. Includes `SxCP Character Age Range`, `SxCP Character Body Pool`, `SxCP Woman Body Pool`, `SxCP Man Body Pool`, `SxCP Eye Color Pool`, and `SxCP Character Clothing`. |
| Hardcore position utility nodes | `node_hardcore_position.py`, imported by `__init__.py` | Position-family pool and action/filter gates for hardcore routes. |
| Formatter utility nodes | `node_formatter.py`, imported by `__init__.py` | Caption naturalizer, Krea2 formatter, and SDXL formatter node wrappers. |
| Insta/OF utility nodes | `node_insta.py`, imported by `__init__.py` | Insta/OF option config and dual prompt-pair node wrappers. |
| Route config utility nodes | `node_route_config.py`, imported by `__init__.py` | Category preset, location/composition pool, location theme, and cast config helpers. |
| Profile/filter utility nodes | `node_profile_filter.py`, imported by `__init__.py` | Generation profile, advanced filter config, and ethnicity list helpers. |

## Drift Audit Helper

The map should be checked when adding nodes, category files, or named pools.
Run:

```bash
python tools/prompt_map_audit.py
python tools/prompt_map_audit.py --quiet
```

Use `--quiet` when you only need pass/fail output in a focused validation pass.

The script does not import ComfyUI. It parses the repo and prints:

- registered display node names and known return names;
- per-JSON category counts;
- named scene/expression/composition pool inventory.
- JSON reference validation for every `scene_pools`, `expression_pools`, and
  `composition_pools` reference;
- item template validation so `{placeholder}` names resolve to `item_axes`.
- category identity validation so custom category names/slugs do not collide
  with built-in selectors, category identities stay unique, and exact
  subcategory selectors cannot become ambiguous.
- effective category route coverage so each normalized category path has
  usable item, scene, expression, composition, and hardcore route metadata
  before runtime fallbacks can hide a gap.
- registered route policy validation: registered route families have SDXL tags,
  caption labels, and valid incompatibility filters before a new action or
  position family can drift between formatter routes.
- location theme camera-profile validation so `Location Theme` presets and
  their scene entries resolve to structured scene-camera profiles instead of
  falling back to generic camera prose.
- route documentation validation so critical route modules are listed in this
  map and the architecture plan, and registered in `SMOKE_CASES` by their
  expected smoke cases.
- node documentation validation so every registered ComfyUI display name appears
  in this route map or the README before the node can silently drift out of
  user-facing docs.
- node registration validation so every concrete `SxCP...` node class in a
  node module is present in that module's class mapping, has a matching display
  mapping, and uses the same mapping key as the class name.

Use its output to spot doc drift after adding a new node or pool. If a new node
or pool appears there but not in this map, update the relevant route table. The
script exits nonzero when JSON pool references, item template axes, category
identities, critical route docs, critical route smoke registrations, or
registered node classes/display names do not resolve.

## Behavioral Smoke Helper

Route behavior should be checked when changing prompt generation, pair assembly,
formatter metadata parsing, trigger handling, expression disabling, or scene
continuity. Run:

```bash
python tools/prompt_smoke.py
```

To inspect or run a focused path while editing one route:

```bash
python tools/prompt_smoke.py --list
python tools/prompt_smoke.py --case krea_format_route_policy --case sdxl_format_route_policy
python tools/prompt_smoke.py --quiet
```

Use `--quiet` when you only need pass/fail output for the selected cases or the
full suite.

The script does not import ComfyUI. It builds representative metadata rows and
pair metadata through the core Python APIs, then verifies:

- generated rows keep prompt, negative prompt, scene, composition, action item,
  and role graph metadata populated;
- a user-added JSON category in a temporary category directory reaches category
  choices, subcategory choices, prompt generation, and formatter metadata
  routes without Python glue code;
- Krea2, SDXL, and natural caption routes use metadata instead of text fallback;
- SDXL and caption trigger handling keeps one trigger;
- negative prompts do not duplicate comma-list items;
- `SxCP Prompt Builder From Configs`-style wiring preserves category, cast,
  generation profile, seed lock, camera, location theme, and composition config;
- same-room Insta/OF continuity keeps prompt text and `hardcore_row.scene_text`
  synchronized;
- camera-aware coworking scene text survives single-row Krea formatting;
- softcore and hardcore pair rows can carry different camera configs without
  collapsing to the same camera phrase;
- POV camera-scene directives suppress normal third-person camera text while
  preserving first-person spatial layout;
- Krea close-interaction routes keep rewritten action wording, avoid raw
  builder labels, and catch invalid surface joins such as `on against a wall`;
- Krea POV penetration routes keep first-person position anchors, suppress
  normal camera text, and preserve composition punctuation before the style
  suffix;
- POV outercourse routes keep constrained boobjob, testicle-sucking,
  penis-licking, handjob, and footjob geometry through Krea formatting;
- POV oral routes keep constrained kneeling, face-sitting, sixty-nine,
  edge-supported, side-lying, and chair oral geometry through Krea formatting
  without recursive viewer wording;
- POV penetration routes keep constrained missionary, cowgirl, reverse-cowgirl,
  doggy, raised-edge, and lotus geometry through Krea formatting;
- POV anal routes keep constrained doggy, bent-over, face-down, standing,
  side-lying, raised-edge, and kneeling rear-entry geometry through Krea
  formatting;
- front/back double-contact routes keep source role graph metadata and Krea
  front/back position wording synchronized;
- climax routes keep source body position, Krea aftermath wording, SDXL family
  tags, and training captions synchronized for face-down, side-lying, lap,
  open-thigh, and front/back placements;
- interaction routes keep source role graphs, Krea prose, SDXL tags, and
  training captions synchronized for manual, clothing transition, body worship,
  camera-performance, aftercare, and group-coordination rows;
- fallback role routes keep solo, women-only, men-only, and mixed-threesome
  source role graphs synchronized with Krea, SDXL, and training-caption outputs;
- expression-disabled rows do not fall back to generated expression text.
- static formatter metadata fixtures keep source-provided action families
  stable across Krea2 prose, SDXL tags, and natural captions even when raw item
  text contains distracting wording.
- profile-save and accumulator endpoint payload handlers are smoke-tested
  without importing ComfyUI, and the reversible index switch keeps pick/input
  and route/output behavior stable.
- Krea2, SDXL, and caption formatter nodes are checked against their public
  Python formatter APIs for metadata-driven pair inputs, so ComfyUI wrappers
  cannot silently drift from route behavior.

## Route Simulation Helper

For prompt-quality edits, run the simulation helper before and after changing
wording or route selection:

```bash
python tools/prompt_route_simulation.py --fail-on-issues
```

The script builds representative single-row and Insta/OF pair routes, formats
them through Krea2, SDXL, and training-caption paths, and reports structured
issues for:

- formatter routes falling back away from metadata;
- formatter route traces exposing selected row metadata such as category,
  action/position family, selected pair side, scene profile, position keys, and
  POV labels;
- raw builder labels leaking into Krea output;
- repeated cast descriptors in training-caption formatter output;
- duplicate negative-prompt comma items;
- softcore prompt noise;
- POV routes emitting third-person camera text or losing first-person wording;
- selected hardcore position filters appearing in `position_keys` but not as
  the primary `position_key`;
- route-family coverage for registered action and position families, excluding
  only documented special cases that have their own smoke fixtures;
- pair seed determinism for Insta/OF metadata and formatted soft/hard outputs;
- pair person, scene, expression, and composition rerolls changing only their
  intended soft/hard metadata axes;
- pair content rerolls changing soft outfit/teaser content while keeping cast,
  scene, hard action, and composition axes stable;
- pair pose rerolls changing hardcore action metadata while keeping cast,
  scene, soft outfit, and composition axes stable;
- pose-axis rerolls changing cast/scene metadata or failing to move pose/action
  metadata.
- multi-seed route sweeps that repeat the same route/noise/seed checks across
  spaced seeds to catch random-pool drift hidden by a single clean seed.

The report also includes a `quality` section. This is the high-level progress
view for path cleanup: it groups route cases by target, action family, and
position family; counts route issues separately from coverage/seed-check
issues; buckets issue types such as label leaks, softcore noise, trace
mismatches, trigger drift, or reroll drift; and lists the weakest cases first
when a sweep finds failures.

Use `--json --include-prompts` when you need the exact raw and formatted text
for debugging a route. Use `--sweep-count 5 --seed-step 101` when changing pool
selection, route terms, or formatter noise rules and you need more than one
seed of evidence.

## Editing Cheatsheet

| Symptom | First file/function to inspect |
| --- | --- |
| Wrong main category/subcategory frequency | Category node config, `category_library.load_category_library`, category JSON weights. |
| Wrong outfit/clothing item | Relevant category JSON, `pair_options.py`, `SxCP Character Clothing`. |
| Nude/clothing state confusing Krea2 | `pair_clothing.py`, then `krea_clothing.natural_clothing_state`. |
| Wrong location | `categories/location_pools.json`, category `scene_pool`, `_scene_pool`. |
| Location good but camera/location layout wrong | `_camera_scene_directive_for_context`, coworking adapter functions. |
| Repeated desk/anchor in POV foreground | Coworking direction/distance/elevation helpers. |
| Wrong expression intensity or action-incompatible character expression | Character slot expression settings, `row_expression.py`, expression pools. |
| Expression appears when disabled | `row_expression.disable_row_expression`, formatter expression extraction. |
| Same hardcore action repeats | Hardcore filter config, `sexual_poses.json` weights, `hardcore_position_config.apply_hardcore_position_config_to_subcategory`. |
| Hardcore interaction beat falls back to penetration/oral | `sexual_poses.json` interaction subcategory, `_role_graph`, and `krea_action_context.is_foreplay_text` / `krea_action_positions.hardcore_pose_anchor`. |
| Raw hardcore prompt position is vague | `sexual_poses.json` item templates and role graph templates. |
| Krea2 hardcore prompt position is vague | `krea_actions.hardcore_action_sentence` or `krea_pov_actions.py`. |
| Man appears described in POV | POV labels, `krea_cast.cast_prose` omit labels, `krea_pov_actions.pov_action_phrase`. |
| Camera prompt missing from Krea2 | Row `camera_directive` / `camera_scene_directive`, then Krea `_camera_phrase`. |
| Trigger missing in Krea2 fallback | `format_krea2_prompt` preserve-trigger fallback behavior. |
| SDXL tags too weak/wrong style | `sdxl_presets.py`, `sdxl_tag_policy.py`, then `sdxl_tag_routes.py`; formatter wrappers `_row_core_tags` / `_soft_tags` / `_hard_tags` should stay compatibility-only. |
| Duplicate punctuation, empty labels, repeated trigger, repeated tag item | `prompt_hygiene.py`, then the route-specific formatter if the repeated content is semantic. |
| Bed/sheet/couch or malformed surface wording leaks into hardcore prompts | `hardcore_text_cleanup.py`, then the relevant category pool/template. |
| Saved profile does not match liked character | Profile save/load path and whether the saved input is row metadata or regenerated slot config. |
| Accumulator preview behavior wrong | `loop_nodes.py` accumulator methods and `web/accumulator_preview.js`. |

## Debug Route Traces

Use these traces to narrow a problem in one pass.

### Hardcore action keeps selecting the same family

1. Check metadata `main_category`, `subcategory`, `content_seed_axis`,
   `action_family`, `position_family`, `position_key`, `hardcore_position_config`,
   `item`, `role_graph`, and `item_axis_values`.
2. If `hardcore_position_config` disabled most families, the repeated action may
   be the only compatible pool left.
3. Inspect `categories/sexual_poses.json` for the selected subcategory,
   `item_templates`, `axes`, and `weight`.
4. If raw `item` differs but Krea output looks identical, inspect
   `hardcore_action_metadata.py` action-family metadata first, then
   `krea_action_context.py` family predicates,
   `krea_action_positions.py` pose anchors/arrangements,
   `krea_action_details.py` item/detail cleanup, `krea_action_climax.py`
   climax cleanup, `krea_action_dispatch.py` family routing, and
   `krea_actions.py` action sentence assembly.

### POV position is spatially wrong

1. Confirm `pov_character_labels` includes the intended man label.
2. Confirm Krea input uses metadata, not plain prompt fallback.
3. Inspect `source_role_graph`, `item`, `source_composition`, and
   `item_axis_values`.
4. Inspect `pov_policy.py` if label omission or POV composition cleanup is
   wrong; inspect `krea_pov.py` if the Krea camera phrase is wrong.
5. Edit `krea_pov_actions.py` if the first-person body geometry is wrong.
6. Edit `sexual_poses.json` if the raw action lacks enough body-position anchor
   for any formatter to infer a good POV prompt.

### Camera disappears or becomes too generic

1. Check row `camera_config`, `camera_directive`, and `camera_scene_directive`.
2. If `camera_detail=off` or `camera_mode=disabled`, missing camera text is
   expected.
3. If POV labels exist, plain `camera_directive` is intentionally suppressed.
4. If a location-aware sentence is missing, inspect
   `_camera_scene_directive_for_context` and the scene detector for that
   location family.
5. If raw metadata has camera text but Krea omits it, inspect `_camera_phrase`,
   `_pair_camera_phrase`, and `_camera_scene_phrase`.

### Nude/clothing wording conflicts

1. Check pair root `hardcore_clothing_state`.
2. Check hard row `item` and `source_role_graph` for access flags.
3. Character slot `hardcore_clothing` overrides pair fallback clothing.
4. For Krea wording, inspect `krea_clothing.natural_clothing_state`.
5. For generation wording, inspect `pair_clothing.py` and
   `character_hardcore_clothing_values`.

### Softcore contains strange no-contact or bed/action leakage

1. Check whether the prompt came from pair softcore or normal category builder.
2. In pair softcore, inspect `softcore_partner_styling`, `softcore_row.item`,
   `softcore_row.pose`, and options `softcore_cast`.
3. If the raw soft prompt contains awkward defensive clauses, inspect
   `pair_output.py`.
4. If Krea adds the awkwardness, inspect `_insta_pair_to_krea`.

### Location composition mentions irrelevant props

1. Check `scene_text` and `composition` separately.
2. If scene is good and composition is bad, edit composition pools, not
   location pools.
3. If a scene-camera adapter rewrote composition, inspect `row_camera.py` first
   for row mutation and `scene_camera_adapters.py` for location-specific prose.
4. If the issue comes from `Location Theme`, edit `location_config.py` / `THEMATIC_LOCATION_PRESETS`.

### Trigger missing after formatting

1. For builder raw prompts, check `trigger` and `prepend_trigger_to_prompt`.
2. For Krea fallback, check `preserve_trigger`; metadata route usually rebuilds
   prose and does not use prompt text as a raw string.
3. For SDXL, trigger handling belongs to `format_sdxl_prompt` style assembly:
   `trigger`, `prepend_trigger_to_prompt`, `preserve_trigger`, and style preset.

## Safe Edit Workflow

Before changing prompt behavior:

1. Capture one raw builder output and the formatter output.
2. Check metadata JSON for `source`, `main_category`, `subcategory`,
   `content_seed_axis`, `scene_text`, `item`, `role_graph`, `source_role_graph`,
   `item_axis_values`, `composition`, `camera_scene_directive`, and
   `pov_character_labels`.
3. Decide whether the bug is selection, raw wording, camera adaptation, or
   formatter rewrite.
4. Edit the smallest owning pool/template/function.
5. Re-run `python tools/prompt_route_simulation.py --fail-on-issues` or a
   similarly small simulation with fixed person/scene/category seeds and only
   the target axis varied.

The repo may have unrelated dirty files during interactive prompt work. Always
stage only the intended files for commits.
