# Semantic String Routing Debt Map

Date: 2026-05-14

Scope: production code paths that infer visual/story semantics from regex,
substring windows, token lists, keyword lists, or phrase replacement. This is a
debt map, not an implementation plan. It should be used to choose one narrow
follow-up area at a time.

## 1. Audit Boundary

This document covers code-side semantic string routing. It intentionally does
not prescribe prompt text changes; those are tracked separately in
`2026-05-14-prompt-hygiene-v1-spec.md`.

Included:

- Runtime or validator code that makes visual/story decisions from open-world
  natural language strings.
- Prompt mutation paths that apply LLM-provided `target -> suggestion` strings
  through blind substring replacement.
- Soft diagnostics that are currently safe but must not be promoted to hard
  gates without a structured SOT.

Excluded:

- Closed-world ID/status/schema/path/security regexes.
- Background ID format checks such as `BG_ID_RE`.
- Hash format or timestamp regexes.
- Strict enum consumption where the enum is already LLM-produced structured SOT.

The dividing rule:

> Regex is acceptable for closed technical syntax. Regex is debt when it tries
> to decide open-world visual meaning such as close framing, gaze, offscreen
> presence, body-part focus, reference usage, or physical/spatial relation.

## 2. Verification Passes Performed

Evidence was gathered from the current checkout:

- Active prompt file source semantics checked through
  `backend/app/modules/prompt_loader.py`.
  - DB first, file fallback: lines 1-5 and 108-137.
  - Stem-specific latest version behavior: lines 7-14.
  - DB rows bypass strict file pack checking: lines 30-33.
- Latest file prompt pack list regenerated from `prompts/_base/*/<latest>`.
  - 58 latest prompt directories.
  - 177 prompt/schema text files.
- Code-side broad scan over `backend/app/**/*.py` for:
  - `re.compile`
  - `_RE`, `_REGEX`
  - `_TOKENS`, `_KEYWORDS`, `_PHRASES`, `_PATTERNS`
  - `.replace(...)`
  - `prompt_lower`, substring membership, proximity windows
  - close/framing/offscreen/reference/phantom related tokens
- Targeted re-read of the known high-risk files:
  - `scene_generation_coordinator.py`
  - `detail_steps.py`
  - `render_prompt_card.py`
  - `shot_visibility.py`
  - `ref_contract_validator.py`
  - `visible_entities_validator.py`
  - `scene_consistency_step.py`
  - `t2i_review.py`
  - `frame_spatial_contract.py`

## 3. Severity Meaning

P0: Runtime routing or fail-fast behavior can change because of open-world text
matching. Fix should become its own implementation area.

P1: Currently diagnostic, LLM-facing hint, canary scope, or secondary consumer.
Do not promote to hard behavior without a structured SOT. Track and clean in a
targeted follow-up.

P2: Acceptable technical regex or legacy/helper residue. Keep unless it starts
feeding open-world routing.

## 4. P0 Inventory

### P0-1. Close-framing regex is a shared routing SOT

Evidence:

- `backend/app/services/scene_generation_coordinator.py:71-81`
  defines `_CLOSE_FRAMING_RE` from natural-language camera text.
- `backend/app/core/steps/detail_steps.py:104` has the matching close-framing
  helper used by scene-detail side logic.
- `backend/app/core/steps/render_prompt_card.py:560-564` imports
  `detail_steps._CLOSE_FRAMING_RE` to derive `render_strategy.framing_scale`.

Current behavior:

- Natural-language camera_direction phrases such as `ECU`, `CU`, `close-up`,
  `클로즈업`, and `손가락이` determine `is_close_framing` and card
  `framing_scale`.
- This affects reference attach policy, render prompt card fields, validators,
  and canaries.

Risk:

- The regex is not a robust SOT for shot scale. It misses unseen synonyms and
  over-triggers scenario-specific phrasing.
- It is already spread across three layers, so a mismatch can route refs
  differently from prompt/card logic.

Fix direction:

- Introduce an LLM-produced structured `framing_scale` or equivalent shot-scale
  enum in `shot_staging`.
- Code must consume the enum, not camera_direction regex.
- Keep the old regex only as temporary diagnostic during migration.
- The first implementation spec should explicitly list every current consumer
  of `_CLOSE_FRAMING_RE` and replace them in one atomic patch.

### P0-2. Close framing only skips chain_bg, not previous-shot refs

Evidence:

- `backend/app/services/scene_generation_coordinator.py:371-381` logs
  `chain_bg ref SKIPPED` for close framing, then still calls
  `build_prev_shot_background_ref`.
- `backend/app/services/scene_generation_coordinator.py:668-678` repeats the
  same behavior in the object/batch path.
- Broad `except Exception` fallback around previous-shot ref remains at
  `scene_generation_coordinator.py:678-682`.

Current behavior:

- Close framing suppresses chain background reference injection.
- Previous-shot reference injection can still happen for any `ref_usage`.

Risk:

- Close-up shots can inherit unwanted people/background bodies from previous
  shots, especially when the previous-shot reference is not `zoom_in_detail`.
- This is a routing policy issue, not just regex detection.

Fix direction:

- Define an explicit `close_framing x ref_usage` attach matrix.
- Preserve `close + zoom_in_detail` because it means same-frame push-in.
- Fail-fast or block `close + exact_background`, `close + atmosphere_reference`,
  and fallback previous-shot refs unless a future structured contract explicitly
  allows them.
- Add `RefContractError` for invalid attach, and re-raise it through coordinator
  broad-except sites.

### P0-3. shot_visibility mutates visible ids from gaze/framing regex

Evidence:

- `backend/app/modules/pipeline/shot_visibility.py:36-94` defines Korean gaze,
  framing, body-part, direction, and offscreen lexicons.
- `backend/app/modules/pipeline/shot_visibility.py:108-132` compiles regexes
  from those lexicons.
- The module docstring says auto-fix is avoided because visible-list mutation is
  silent SOT mutation: `shot_visibility.py:22-24`.
- The calling path has historically altered `visible_entity_ids` from these
  detections in `shot_director` integration.

Current behavior:

- Text patterns in shot descriptions and camera_direction can decide whether an
  entity is visible/offscreen.

Risk:

- This is exactly the open-world semantic regex pattern: a fixed Korean/English
  list tries to infer visibility from arbitrary screenplay text.
- False positives block canaries and downstream scene_detail execution.

Fix direction:

- Stop mutating visible ids from regex output.
- Convert to drift diagnostics first.
- Long-term SOT should be a structured shot-director/shot-staging output:
  `visibility_relation`, `offscreen_reason`, or equivalent.
- Any auto-correction must be LLM-produced structured data, not code regex.

### P0-4. ref_contract_validator classifies "from the reference" by token window

Evidence:

- Token groups live in `backend/app/core/ref_contract_validator.py:47-62`.
- `_FROM_THE_REFERENCE_RE` is line 64.
- Window and generic-instruction logic is lines 79-88 and 150-153.
- `classify_from_the_reference()` uses a 30-character local window and 60-character
  fallback at lines 156-190.
- Phantom guard consumes this classification at lines 425-445.

Current behavior:

- The phrase `from the reference` is classified as `character`, `background`,
  `object`, or `ambiguous` from nearby words such as `face`, `wall`, `phone`.

Risk:

- This improved earlier false positives, but it still routes open-world
  reference semantics through a noun window.
- Ambiguous cases only warn; classified cases can raise hard `RefContractError`.

Fix direction:

- Prefer attached ref metadata and RenderPromptCard requirements over prompt
  phrase classification.
- Keep this classifier only as a transitional phantom-guard diagnostic.
- Do not expand the token lists.
- Future replacement: scene_detail should echo structured reference intents or
  consume render contracts; validator should compare required ref kinds/ids, not
  nearby nouns.

### P0-5. Prompt-side owned-object judge depends on verb whitelist

Evidence:

- `prompts/_base/scene_detail_owned_judge/3.202605051746/system.md:18-61`
  defines redraw/anchor decision rules from verb and phrase lists.
- The judge is LLM-side, but it is still a closed phrase inventory used to decide
  a semantic category.

Current behavior:

- It asks the LLM to classify redraw behavior from known verbs and phrases.

Risk:

- New verbs or non-English phrasings can silently pass.
- This repeats the "closed list of open-world verbs" failure mode.

Fix direction:

- Do not patch this in the first framing-scale area.
- Future area should introduce `owned_object_usage[]` or equivalent structured
  echo from scene_detail, then have the judge verify structured claims instead
  of mining prose.

### P0-6. body-part/face close-up exemption is regex and substring based

Evidence:

- `backend/app/core/visible_entities_validator.py:139-145` defines
  `_FACE_CLOSE_UP_PATTERNS`.
- `backend/app/core/visible_entities_validator.py:212-218` scans
  `id_policy.body_part_focus_rule.trigger_phrases` as substrings in
  `t2i_prompt`.

Current behavior:

- Forward enforcement can be exempted when body-part focus trigger phrases
  appear, except face/eye close-up regex cancels the exemption.

Risk:

- The enforcement decision is a semantic policy but relies on phrase matching.
- It is a sibling of the close-framing regex problem and should be resolved on
  top of the same structured framing/body-focus SOT.

Fix direction:

- First introduce structured `framing_scale` / `focus_scope` / `body_part_focus`
  fields.
- Then remove face-close-up regex from the exemption gate.
- Code should consume `id_policy.body_part_focus_rule` and structured shot-scale
  fields, not search the prompt prose.

### P0-7. perception_mode is compared as string tokens in render_prompt_card

Evidence:

- `backend/app/core/steps/render_prompt_card.py:1292` checks
  `(perception_mode or "").lower() in ("reflection", "mirror",
  "through_device", "projection")`.

Current behavior:

- Representation/reflection behavior is driven by a free string narrowed by
  token comparison.

Risk:

- This is a dual-SOT risk with shot-staging prompt vocabulary and scene-detail
  representation behavior.
- It directly affects face/body displacement and representation cases.

Fix direction:

- Convert `perception_mode` to a schema enum, or derive a structured
  `representation_binding` render contract.
- Do not add more tokens to the tuple.
- Treat this as part of the id-policy/framing or representation-binding area,
  not as a quick local patch.

## 5. P1 Inventory

### P1-1. Variant/transformation naming is distributed across prompts

Evidence:

- `prompts/_base/entity_all/4.202603310100/character.md:26-31`.
- `prompts/_base/entity_extractor_v2/9.202605130226/system.md:16-36`.
- `prompts/_base/entity_relation/2.202603301800/system.md:11-13,30-37`.
- `prompts/_base/outlook_extractor/11.202603311724/phase1.md:24`.
- `prompts/_base/visual_world_rules/6.202605021400/system.md:30-36,51`.

Current behavior:

- Transformation, possession, ghost, monster, and avatar-like cases are described
  independently in several prompts.

Risk:

- The concepts are legitimate, but distributed nomenclature can drift. One step
  may split variants that another step expects to bind through rules.

Fix direction:

- Make `visual_world_rules.rules[].rule_type` the conceptual SOT.
- Other prompts should say "follow visual_world_rules" and avoid their own
  mythology-specific lists except as generic examples.

### P1-2. frame_spatial_contract phrase diagnostic is soft and should stay soft

Evidence:

- `backend/app/core/frame_spatial_contract.py:25-41` maps enum zones/depths to
  phrase variants.
- `backend/app/core/frame_spatial_contract.py:309-332` returns diagnostics only;
  no raise.

Current behavior:

- It checks if t2i_prompt mentions label/zone/depth phrases.

Risk:

- Safe today because it is soft. If promoted to hard, it becomes another
  open-world phrase classifier.

Fix direction:

- Keep v1 as warning only.
- Hard validation must use a structured echo, not phrase presence.

### P1-3. render_prompt_card spatial token lists are LLM-facing/canary hints

Evidence:

- `backend/app/core/steps/render_prompt_card.py:331-438` defines spatial camera,
  frame-edge, fg/bg, interaction, shared-anchor, close, and wide token lists.

Current behavior:

- Comments state these are LLM-facing hints or canary scope filters.

Risk:

- Acceptable while not used as production semantic SOT.
- Dangerous if later used to decide hard routing.

Fix direction:

- Do not expand these lists for new behavior.
- Future structural replacement should consume `frame_spatial_contract` or
  render contracts.

### P1-4. visible_entities_validator uses closed entity ID anchors

Evidence:

- `backend/app/core/visible_entities_validator.py:540-550` iterates prompt text
  by visible entity base/name and outlook candidates.

Current behavior:

- It validates ID presence/absence around known entity IDs/names.

Risk:

- This is mostly closed-world validation and can remain.
- Risk appears when name-window matching starts inferring semantic visibility
  rather than validating an existing entity contract.

Fix direction:

- Keep ID-anchored checks.
- Avoid adding new natural-language semantic windows.

### P1-5. t2i_review applies blind substring replacements

Evidence:

- `backend/app/modules/pipeline/t2i_review.py:383-403` replaces entity
  `target -> suggestion`.
- `backend/app/modules/pipeline/t2i_review.py:407-451` replaces scene
  variation prompt text by `item_id` + target substring.
- Prompt rules require exact target/suggestion strings in
  `prompts/_base/t2i_review/3.202605121200/scene_system.md:87-90`.

Current behavior:

- The LLM detects issues and returns exact substrings, then code performs
  `.replace()`.

Risk:

- `item_id` fixed fan-out, but target replacement can still rewrite more than
  intended if target appears multiple times.
- The prompt also trains the LLM with very concrete examples.

Fix direction:

- Do not expand target/suggestion mutation.
- Future replacement should use span positions, patch IDs, or issue-code-only
  diagnostics that trigger regeneration rather than text surgery.

### P1-6. scene_consistency close/detail classification uses element-id and description tokens

Evidence:

- `backend/app/core/steps/scene_consistency_step.py:65-72` defines
  `_ELEMENT_ID_CLOSE_REGEX`.
- `backend/app/core/steps/scene_consistency_step.py:73-80` defines
  `_DESCRIPTION_CLOSE_KEYWORDS`.

Current behavior:

- It identifies close/detail element IDs and descriptions from suffix and
  phrase patterns.

Risk:

- Less urgent than runtime reference routing, but it is another open-world
  textual close/detail classifier.

Fix direction:

- Treat as a follow-up after framing-scale SOT exists.
- Prefer explicit scene_consistency field for element scale/scope.

## 6. P2 / Keep Inventory

These are regex/string sites found during the audit that should **not** be
treated as semantic-routing debt unless they start consuming open-world visual
meaning. They are listed explicitly to prevent broad "delete every regex" work.

### P2-1. Reference-image label parser

Evidence:

- `backend/app/services/prompt_service.py:43-58` defines exact parsing for
  `Image N (<tag>): <brief>` and `Image N: <bare label>`.
- `prompt_service.py:60-70` checks closed character-ref header tags and bare
  prefixes.

Why keep:

- This parses machine-produced reference labels.
- It deliberately avoids substring matching in the free-text brief.

Guard:

- Keep it closed to producer label syntax.
- Do not add open-world visual nouns here.

### P2-2. Shot-id parser for background loader

Evidence:

- `backend/app/services/scene_checkpoint_loaders.py:129` defines
  `_SHOT_ID_RE = ^S(\d+)_Shot(\d+)$`.
- `scene_checkpoint_loaders.py:167-171` uses it to turn explicit shot ids into
  map keys.

Why keep:

- This is technical ID parsing, not semantic inference.

Guard:

- Keep it restricted to `S##_Shot##` syntax.

### P2-3. Hash and prop-id format checks

Evidence:

- `backend/app/core/steps/render_prompt_card.py:528-530` checks 16-char
  lowercase hash format.
- `render_prompt_card.py:1806-1865` checks Area B prop short IDs with
  `_PROP_ENTITY_ID_RE = ^P[0-9]+$`.

Why keep:

- These are closed format checks for hash/entity IDs.
- They are fail-fast shape validation, not open-world semantic classification.

Guard:

- Do not mix prop-name nouns into this path.

### P2-4. Background planning technical ID filters

Evidence:

- `backend/app/modules/pipeline/background_master_plan.py:17-22` rejects
  non-ASCII ids and validates safe snake-case ids.
- `background_master_plan.py:131-138` validates `fp_id` with the canonical
  `LLM_INTENT_ID_RE`.
- `backend/app/core/steps/background_render_step.py:50-68` selects D6 strict
  `BG_ID_RE` or legacy-safe bg-id regex by checkpoint marker.

Why keep:

- These regexes validate technical ids and transition compatibility.

Guard:

- Do not use these helpers to infer location semantics or background content.

### P2-5. Insert/cutaway detection is lower priority but should not expand

Evidence:

- `backend/app/core/steps/render_prompt_card.py:545-557` lazily compiles
  insert/cutaway hints.
- `render_prompt_card.py:560-566` then derives framing scale from close regex
  first and insert regex second.

Why not P0:

- The current largest runtime risk is close-framing reference routing.
- Insert/cutaway should be revisited after the structured framing/focus SOT
  exists.

Guard:

- Do not expand this list as a quick fix.
- Migrate it under the same structured framing/focus SOT as P0-1.

### P2-6. Generic replace calls outside visual prompt mutation

Evidence:

- Broad scans found `.replace(...)` in file/path/string formatting helpers,
  background prompt template filling, DB timestamp handling, and markdown/html
  formatting.

Why keep:

- These are not semantic prompt mutations.

Guard:

- Only `t2i_review` prompt mutation remains in this debt map because it changes
  generated visual prompts based on LLM-provided target/suggestion strings.

## 7. Cross-Cutting Findings

### 7.1 The first structural prerequisite is framing-scale SOT

P0-1, P0-2, P0-6, and part of P0-7 all depend on a reliable structured notion
of shot scale/focus. Therefore the first implementation area should not be
"remove every regex". It should be:

> Introduce structured framing/focus SOT, then replace close-ref routing and
> body-part exemption gates that currently depend on prose matching.

### 7.2 Do not replace regex with another noun list

The purpose is not to move from code regex to prompt lists such as "photo,
document, map, phone". For open-world visual semantics, the replacement must be
LLM-produced structured fields or render contracts.

### 7.3 Fail-fast beats silent skip

Where a routing decision affects attached refs or required refs, invalid
combos should fail fast. Silent skip is only acceptable for documented
non-contract diagnostics.

### 7.4 Soft phrase diagnostics are allowed, hard phrase gates are not

`frame_spatial_contract.phrase_diagnostic()` is acceptable because it returns a
warning list. Converting it to a hard gate would reintroduce the same class of
bug.

## 8. Recommended Implementation Order

### Area 1. Framing/Focus SOT

Goal:

- Replace `_CLOSE_FRAMING_RE` as production SOT.
- Define structured `framing_scale` / `focus_scope` / related enum in
  shot_staging or an equivalent producer.
- Update `scene_generation_coordinator`, `detail_steps`, and
  `render_prompt_card` consumers atomically.

Must include:

- Close x ref_usage matrix.
- Preservation of `zoom_in_detail`.
- Fail-fast for unsafe close previous-shot refs.
- Regression test for batch and single regenerate paths.

### Area 2. shot_visibility drift, no mutation

Goal:

- Stop regex-driven visible ID mutation.
- Keep drift diagnostics and fail-fast validation only.
- Move visibility/offscreen intent to structured SOT.

### Area 3. ref-contract phantom guard replacement

Goal:

- Reduce `classify_from_the_reference()` to warning-only or replace with
  attached metadata/render-contract comparison.
- Do not expand classifier token lists.

### Area 4. body-part/face exemption cleanup

Goal:

- Consume structured body-focus/focus-scope fields.
- Remove `_FACE_CLOSE_UP_PATTERNS` from enforcement decisions.

### Area 5. t2i_review mutation redesign (Area #6 v1 — CLOSED, 2026-05-18+)

**Status**: CLOSED (2026-05-18+, W1-W3 commits + W1 per-wave APPROVED + W1..W3c range review APPROVED + canary 5 PASS + residue gate scoped strict 0).

**Wave commits**: W1 atomic `8469511` + W2 docs `7e2b8cf` + W3a canary `b9a64c7` + W3b residue gate `85d2c0a` + W3c closure (본 commit).

**Fix-direction applied** (Track B Tier 2 #4): L-3 verbatim — `target.replace()` mutation 폐기 + 5 core fields structured diagnostic. v1 = signal-only emit, in-step regeneration 0, cascade 1-pass policy 변경 0 (외부 orchestrator force 의무).

**Surface**: `backend/app/modules/pipeline/t2i_review.py` + `backend/app/core/steps/t2i_review_step.py` + `backend/app/core/step_manifest.py` schema_version: 1.

**Prompt**: `prompts/_base/t2i_review/6.202605181200/` 4 file (`scene_system.md` + `scene_schema.json` + `entity_system.md` + `entity_schema.json`), entity 4.x stem drift 해소.

**Closed boundary**: Patch A/B/B1 5 functions 보존 (`_build_item_id_map` invoked / `_refresh_scene_detail_sentinels` + `_assert_card_hash_unchanged` + `_save_checkpoint_data` defensive / `_capture_card_hash_state` invoked) — defensive regression guard.

**Carry**: Area #7 Prompt Hygiene v2 (placeholder abstraction noun broader cleanup, P-6 closure 미선언).

Goal (original):

- Move from blind substring replacement to structured patch IDs/spans or
  regeneration diagnostics.

## 9. Do Not Do

- Do not implement all P0/P1 items in one patch.
- Do not expand regex/keyword lists as a "fix".
- Do not promote soft phrase diagnostics to hard gates.
- Do not combine prompt hygiene with code-side routing migration.
- Do not add case-named contracts such as `photo_front`, `dead_body_pose`, or
  `boat_identity`. Use generic visual requirement dimensions if a contract is
  needed.

## 10. Completion Criteria For This Map

This map is complete when:

- Every P0/P1 item above has file:line evidence.
- Each item has a replacement direction that avoids open-world code regex.
- The first implementation area is narrowed to one structural prerequisite.
- Prompt-side contamination is explicitly separated into the prompt hygiene spec.
