# Semantic String Debt LLM Audit Findings

- result chunks: `20`
- findings: `36`

## `backend/app/core/steps/render_prompt_card.py`

- `584` **P2 / scenario_dependent_prompt**
  - evidence: "(low / hip-height / overhead / ground level / quay level / floor level / from above)"
  - why: The prompt includes 'quay level' as a specific camera height example. This is scenario-specific pollution (maritime/port setting) that can bias the LLM's spatial reasoning in unrelated scenarios.
  - fix: Use generic camera height terms (e.g., 'surface level', 'eye level') or abstract placeholders.

- `1138-1140` **P2 / llm_closed_list_instruction**
  - evidence: "ethnicity": list(_ID_ETHNICITY_COMPONENTS), "age_band": list(_ID_AGE_BANDS)
  - why: The prompt asks the LLM to classify open-world demographic meaning from a closed list of ethnicities and age bands. This list may drift from the project's actual demographic SOT or bias the LLM against unlisted groups.
  - fix: Pass the allowed demographic vocabulary as a dynamic context field derived from the project's visual world rules rather than using a hardcoded module constant.

- `1187-1191` **P2 / llm_closed_list_instruction**
  - evidence: "triggered by 'focus on / close on / tight on / detail on' phrasing"
  - why: The prompt defines a semantic rule (body-part focus) based on a closed list of trigger phrases. This forces the LLM to act as a brittle string-pattern classifier rather than using structured intent.
  - fix: Define the 'body-part focus' state as a structured boolean or enum field in the input staging data rather than inferring it from phrasing.

- `1326-1334` **P2 / llm_closed_list_instruction**
  - evidence: "do not use any of: 'the existing X', 'from the reference', 'use the X from the reference', ..."
  - why: The prompt defines 'invalid phrasing' for close framing using a closed list of specific substrings. This is a brittle semantic classifier for output validation that may miss variations or over-penalize valid prose.
  - fix: Provide a general principle about not referencing absent images and use a post-generation semantic validator rather than exact phrase blacklists.

- `1494-1498` **P1 / blind_string_mutation**
  - evidence: "substitution": "replace the common-noun person reference inside fixed_elements[i].description ... with the matched C## or C##O##"
  - why: This instructs the LLM to perform a substring replacement on natural-language text based on a fuzzy semantic category ('common-noun person reference'). This is a contract for blind semantic mutation that relies on the LLM to correctly identify and slice arbitrary prose.
  - fix: Use a formal placeholder syntax in the description (e.g., [ENTITY_ID]) and have the LLM map IDs to those placeholders, or perform the replacement in code using structured entity mapping.

## `backend/app/modules/pipeline/entity_extractor_v3.py`

- `84-169` **P2 / schema_or_enum_drift**
  - evidence: ENTITY_DETAIL_SCHEMA, metadata_json, entity_type
  - why: The schema defines entity_type as a generic string and makes it required, but the code in _gen_t2i ignores the LLM's output for this field in favor of an upstream variable. Furthermore, metadata_json uses permissive anyOf schemas for location and visual_identity, while comments (lines 96-104) specify strict per-type shapes (e.g., character must have null location) that are not enforced by the schema or validated by the code before forwarding.
  - fix: Use a string enum for entity_type in the schema. Refactor metadata_json to use a discriminator or separate schemas per entity type to ensure the LLM follows the expected structure for characters vs locations vs props, and validate these constraints in _gen_t2i.

## `backend/app/modules/pipeline/scene_extractor_v2.py`

- `330` **P1 / semantic_string_judgment**
  - evidence: pos = fulltext.find(start_text, search_from)
  - why: Uses an LLM-generated natural language snippet (start_line_text) as a brittle anchor to determine scene boundaries in the original scenario text. If the LLM slightly alters punctuation or characters, the match fails, leading to incorrect scene routing.
  - fix: Use token-based offsets or unique line identifiers in the prompt to allow the LLM to return stable indices rather than arbitrary prose snippets.

- `450-451` **P1 / semantic_string_judgment**
  - evidence: if split_text in scene_text: split_pos = scene_text.index(split_text)
  - why: Similar to the segmentation anchor, this uses LLM-generated prose to find a split point for long scenes. Failure to match exactly results in keeping the original segment, which is a routing decision based on brittle string matching.
  - fix: Implement fuzzy matching for anchors or use structured line-by-line analysis to identify split points.

- `817-822` **P1 / blind_string_mutation**
  - evidence: if marker not in current_t2i ... var["t2i_prompt"] = current_t2i.rstrip() + " " + suffix
  - why: Blindly appends a suffix to the generated T2I prompt prose if a specific character/outlook marker pattern is missing. This is a post-hoc semantic mutation of generated text based on a brittle substring check.
  - fix: Instruct the LLM to include markers in a structured field or use a template-based prompt assembly method that ensures markers are present without post-generation string injection.

## `backend/app/modules/semantic_contract_router.py`

- `22-107` **P1 / semantic_string_judgment**
  - evidence: IMMOBILIZED_GAZE = frozenset({"dead", "unconscious", "severely_injured"}) ... if gaze not in IMMOBILIZED_GAZE
  - why: Overloaded semantic channel. The 'gaze_target' field, intended for spatial orientation, is used to carry physical state information. This brittle string matching routes to 'immobilized' logic which enforces strict polarity preservation and forbids active reactions in the sanitizer.
  - fix: Transition to the planned 'subject_state.immobility_state' structured field as noted in the file's docstring and use a proper enum instead of overloading the gaze field.

## `backend/app/modules/t2i_visual_converter.py`

- `128-159` **P1 / blind_string_mutation**
  - evidence: 1. 각 씬 아래의 '★ 엔티티 목록'에 있는 이름만 [이름] 마커 사용 가능 ... 3. [이름]은 목록의 이름을 공백 포함 정확히 복사 ... ★ 목록에 없는 대상은 보통명사로 서술 ([] 마커 금지)
  - why: The prompt establishes a brittle contract requiring the LLM to output exact substrings (entity names in brackets) to facilitate downstream string replacement. This is a 'contract for blind semantic mutation' where minor LLM variations (spacing, typos) break the reference attachment logic. It also forces the LLM to perform semantic routing (marker vs. common noun) based on a dynamic string list.
  - fix: Transition to a structured output where the LLM identifies entities by ID in a separate field, or use a more robust placeholder system that does not rely on the LLM perfectly replicating natural language names.

## `backend/app/services/scene_generation_coordinator.py`

- `412` **P1 / blind_string_mutation**
  - evidence: var_t2i = _rewrite_t2i_helper(var_t2i, _sid_to_img, _sid_info)
  - why: This performs blind substring replacement on generated T2I prompt text to inject reference labels (e.g., replacing 'Character A' with 'the character from Image 1'). This is brittle for natural language and can lead to grammatical or semantic errors.
  - fix: Use a template-based approach where placeholders are inserted during initial prompt generation, or use an LLM-based rewriter that understands context.

- `713` **P1 / blind_string_mutation**
  - evidence: var_t2i = _rewrite_t2i_helper(var_t2i, _sid_to_img, _sid_info)
  - why: Duplicate of the blind mutation mechanism used in the batch generation loop, applying string replacement to generated variation prompts.
  - fix: Centralize prompt mutation into a structured assembly service that avoids raw string replacement on generated prose.

- `718-719` **P1 / semantic_string_judgment**
  - evidence: if shot_name and shot_name.lower() not in var_t2i.lower(): var_t2i = f"[Camera: {shot_name}] {var_t2i}"
  - why: This uses a brittle substring check on generated natural-language prompt text to decide whether to mutate the prompt with a camera directive. It fails to account for semantic variations or partial matches in the open-world prompt prose.
  - fix: Pass the shot name as a structured metadata field to the generation pipeline or use an LLM to determine if the camera directive is redundant.

## `backend/app/services/scene_reference_service.py`

- `65-66` **P2 / semantic_string_judgment**
  - evidence: _re.search(r'character\s+(C\d{2,3}(?:O\d{2,3})?)', label)
  - why: Internal reference indexing relies on parsing string labels to infer entity identity. This creates brittle coupling between the label-generation logic and the indexing logic, where a change in label formatting breaks reference mapping.
  - fix: Pass structured metadata (e.g., entity_id, short_id) alongside the label instead of parsing the label string.

- `111-128` **P1 / blind_string_mutation**
  - evidence: rewritten = _re.sub(pattern, replacement, rewritten)
  - why: Performs blind substring replacement of character/object IDs within generated T2I prompt prose. This can lead to corruption if IDs appear in unintended contexts (e.g., as part of other words or descriptions) within the prose.
  - fix: Use a template-based prompt generation system or a token-aware replacement mechanism that respects natural language boundaries.

- `367-444` **P1 / semantic_string_judgment**
  - evidence: _re.finditer(r'(C\d{2,3})(O\d{2,3})', t2i_prompt), r'\[\[([^\]]+)\]\+\[([^\]]+)\]\]', and outlook_name == "미지정"
  - why: Reference resolution is performed by parsing generated natural-language prompt text with regex and checking for specific Korean literals ("미지정"). This couples the prompt's prose style and language to the logic that attaches reference images, making it brittle to changes in prompt engineering.
  - fix: Pass structured reference requirements (e.g., required_refs) from the coordinator instead of inferring them from the prompt text.

- `849-988` **P1 / semantic_string_judgment**
  - evidence: ref_usage = dep_info.get("ref_usage", "") and ca.get("gaze_target", "") in ("unconscious", "dead", "severely_injured")
  - why: Uses string-based classifiers to route visual meaning and select complex prompt instructions. The gaze_target field is used as an overloaded semantic channel where physical state (death/injury) is encoded in a field intended for gaze direction, which then drives reference attachment and prompt text selection.
  - fix: Introduce explicit, typed fields for physical state (e.g., physical_status) and use a structured enum for ref_usage.

## `prompts/_base/background_chain_planning/4.202604291315/system.md`

- `21-25` **P1 / llm_closed_list_instruction**
  - evidence: INDOOR / ENCLOSED / FIXED-SET (room, hallway, store interior, office, kitchen, basement, closed vehicle interior, fixed studio set, etc.) ... DETACHED OPEN AREA ... (a road far from any building, an unrelated forest, a wide beach, a public street block, a mountain trail, a public square not attached to a tracked building)
  - why: The prompt uses a closed list of location examples to instruct the LLM to perform a semantic classification that determines the 'skip_chain' routing logic. This makes the pipeline's core branching logic dependent on the LLM's interpretation of open-world descriptions against a brittle list of examples.
  - fix: Define a structured 'location_category' enum in the input metadata (e.g., 'architectural' vs 'detached') and use that to drive the skip_chain decision upstream or as a strict instruction.

- `45-105` **P1 / llm_closed_list_instruction**
  - evidence: Close-ups, prop inserts, hand-scale plates, 'background plate' shots, photograph inserts... DO NOT need their own background image
  - why: This defines a semantic classifier for shot types that determines whether a shot should reuse a parent background or create a new node. It relies on the LLM matching these specific concepts in natural language descriptions to decide graph topology.
  - fix: Introduce a 'shot_kind' enum in the input schema and define the node-reuse policy based on those technical categories.

- `48-106` **P2 / scenario_dependent_prompt**
  - evidence: different room state (clean / lived-in / disturbed / heavily-ransacked) ... different active fixed-element zones (e.g. 'open window with torn curtain' vs. 'closed window')
  - why: The prompt contains concrete scenario-specific examples ('heavily-ransacked', 'torn curtain') that function as semantic classifiers for state-based node splitting. These examples bias the LLM towards specific story tropes and may not generalize well to other genres.
  - fix: Replace scenario-specific examples with abstract state categories or project-neutral descriptions of visual variance.

## `prompts/_base/entity_extractor_v2/9.202605130226/system.md`

- `97-101` **P2 / llm_closed_list_instruction**
  - evidence: allowed_space_keys 는 controlled vocab 안에서 선택: main / kitchen / rooftop / stairs / yard / exterior / office
  - why: The prompt forces the LLM to classify complex open-world locations into a very narrow list of 7 strings. Locations like 'bedroom', 'hallway', 'dungeon', or 'forest' are forced to fallback to 'main', losing semantic precision that line 106 indicates is used for deterministic background ID generation.
  - fix: Replace the hard-coded list with a more comprehensive architectural vocabulary or allow the LLM to emit descriptive sub-space keys that are normalized by a downstream service.

## `prompts/_base/location_consistency/2.202604201230/system.md`

- `11-34` **P1 / blind_string_mutation**
  - evidence: 이 문장은 나중에 scene_detail이 t2i_prompt에 그대로 삽입하므로 ... 영어로 작성 (T2I 프롬프트에 직접 삽입)
  - why: The prompt establishes a contract for blind string insertion of LLM-generated natural language prose into a downstream T2I prompt. This is fragile as any failure in the LLM's filtering logic will pollute the final image generation prompt with inconsistent or unwanted elements across an entire episode.
  - fix: Instead of blind insertion of prose, use a structured representation of location attributes that the downstream component can compose safely, or use a template-based approach with validated slots.

- `23-83` **P1 / llm_closed_list_instruction**
  - evidence: 절대 포함 금지 — 환경 상태는 씬마다 달라짐 ... 이 문장에 날씨·조명·인물이 섞여 있는가? — YES라면 그 부분만 삭제
  - why: The prompt instructs the LLM to act as a semantic classifier and filter, stripping out open-world concepts (weather, lighting, characters, etc.) based on a closed list of categories. This relies on the LLM's ability to correctly categorize arbitrary prose into these buckets to maintain 'location consistency' through string manipulation.
  - fix: Define the 'fixed' attributes of a location in a structured schema (e.g., materials, architecture_style, layout) and have the LLM populate those fields, rather than asking it to filter a natural language description.

- `43-57` **P2 / scenario_dependent_prompt**
  - evidence: A compact one-room apartment with faded wallpaper and a worn linoleum floor ... utility pole wrapped with tangled wires
  - why: The examples contain concrete, specific props and architectural details (faded wallpaper, linoleum, utility poles) that are not abstract placeholders. These can bias the LLM towards specific urban/contemporary styles even when the target scenario is different (e.g., sci-fi or historical).
  - fix: Use more abstract or diverse examples, or replace specific props with placeholders like [material] or [specific_prop] to demonstrate the desired level of detail without biasing the content.

## `prompts/_base/scene_extractor_v2/18.202605150955/system.md`

- `47` **P1 / semantic_string_judgment**
  - evidence: (<몽타주> 표시 또는 빠른 컷 전환)
  - why: Instructs the LLM to classify a scene as 'montage' based on the presence of a specific Korean string pattern in the input scenario text, which is a brittle way to infer cinematic structure.
  - fix: Instruct the LLM to identify montage sequences based on semantic characteristics (e.g., rapid time jumps, multiple locations) rather than specific bracketed markers.

- `60` **P2 / scenario_dependent_prompt**
  - evidence: rule_type=possession, remote_identity, visible_body
  - why: Hardcodes a specific 'possession' or 'remote control' story mechanic into the base scene extraction logic. This is scenario pollution that assumes the existence of specific supernatural or sci-fi tropes in arbitrary scenarios.
  - fix: Move scenario-specific entity relationship rules to a dynamic configuration or a specialized prompt layer rather than the base system prompt.

## `prompts/_base/scene_extractor_v2/18.202605150955/turn_scene_detail.md`

- `34-38` **P1 / llm_closed_list_instruction**
  - evidence: 얼굴/형태 식별 불가 인물 — short_id 사용 금지 ... 창문 뒤 실루엣, 역광으로 검은 윤곽만 보임, 안개 속 먼 인물
  - why: This instruction asks the LLM to perform a semantic classification of a character's visual state (identifiability) from scenario prose and use that to decide whether to include or exclude a short_id. This routes ID enforcement policy based on brittle natural-language interpretation.
  - fix: Introduce a structured 'is_identifiable' or 'visual_state' boolean/enum in the character-scene mapping from the upstream extractor to explicitly control ID usage.

- `60-95` **P2 / schema_or_enum_drift**
  - evidence: camera_effect 필드에는 기법 이름을 써도 되지만 ... camera 구도 선택지 ... 색감 선택지
  - why: The prompt contains hardcoded lists of camera angles and lighting styles (e.g., 'soft_light_intimate', 'warm amber') and provides manual prose expansions for them. This creates drift between the prompt's vocabulary and any central schema or downstream consumer of the 'camera_effect' field.
  - fix: Centralize the camera and lighting vocabulary in a shared schema/enum and inject the allowed values and their descriptions into the prompt dynamically.

- `105-109` **P1 / llm_closed_list_instruction**
  - evidence: visible_entities 주의사항: 이 씬의 화면에 물리적으로 존재하는 대상만 넣으세요 ... container descriptor 안의 인물들이 transport vehicle에 타고 있다면
  - why: The prompt instructs the LLM to filter the 'visible_entities' list based on a semantic interpretation of physical presence versus mere mention in the text. This logic (e.g., container vs. vehicle) is a semantic classifier for entity membership.
  - fix: Move visibility logic to a dedicated scene-parsing step that outputs a structured visibility flag for each entity, rather than asking the T2I prompt generator to infer it from prose.

## `prompts/_base/shot_dependency_t2i/7.202605151200/system.md`

- `20-59` **P2 / llm_closed_list_instruction**
  - evidence: ref_usage (zoom_in_detail, exact_background, atmosphere_reference)
  - why: The prompt asks the LLM to classify open-world visual relationships between shots into a closed list of three semantic categories. This is a semantic classifier that routes downstream image generation behavior.
  - fix: Ensure these categories are part of a central SOT enum and provide more objective, technical criteria for classification to reduce semantic drift.

- `146-163` **P1 / semantic_string_judgment**
  - evidence: human / person / character / body / figure / man / woman / detective / prisoner / child / person silhouette
  - why: The prompt uses a brittle list of natural-language words to validate the semantic content of the 'label' field. If any of these words appear, the output is rejected (fail-fast), which is a string-pattern-based judgment of open-world meaning (personhood).
  - fix: Instead of a negative lexicon, use a structured schema where characters and environment/props are separate entities, and rely on the LLM's understanding of the schema rather than keyword filtering.

- `161` **P2 / schema_or_enum_drift**
  - evidence: immobilized_character / character / pose 등 enum 외 값 절대 금지
  - why: The prompt explicitly forbids specific strings that are not in the defined enum, suggesting that the LLM frequently emits these values or that they exist in other parts of the system, indicating a lack of strict schema enforcement.
  - fix: Enforce the enum at the schema level (e.g., JSON schema) rather than using negative instructions in the prompt.

## `prompts/_base/visual_world_rules/6.202605021400/system.md`

- `51` **P1 / llm_closed_list_instruction**
  - evidence: rule_type: possession, transformation, ghost, projection, superpower, body_deformation, time_period, costume, technology, other
  - why: This forces the LLM to map complex, open-world narrative tropes into a fixed set of semantic categories. These categories then trigger hardcoded visual logic (e.g., the possession rule in line 31), which is brittle when applied to diverse storytelling.
  - fix: Allow the LLM to describe the visual phenomenon naturally or use a more extensible schema that defines visual properties (transparency, facial identity, etc.) rather than narrative tropes.

- `57-73` **P1 / semantic_string_judgment**
  - evidence: director_notes (물리적 존재 여부를 판단할 때 혼동할 수 있는 유형적 원칙)
  - why: The prompt instructs the LLM to generate natural-language rules (e.g., 'hallucinations are not physical') to resolve ambiguity in physical presence. This delegates core pipeline routing and visibility logic to generated prose, which is difficult to validate and prone to drift.
  - fix: Define a structured 'visibility_policy' field with explicit flags (is_physical, is_visible_to_all, is_screen_only) rather than relying on generated 'notes' to guide the scene director.

- `98` **P2 / scenario_dependent_prompt**
  - evidence: All human characters are <region-derived demonym> unless stated otherwise.
  - why: This instruction forces a global demographic bias into the t2i_context based on a single extracted 'region' string. It assumes a monolithic ethnicity/nationality for the entire scenario, which can lead to incorrect visual generation for multi-cultural or international stories.
  - fix: Move demographic defaults to individual character metadata or allow the LLM to specify a distribution rather than a global override.
