@mytechtoday/augment-extensions 2.3.7 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/augment-extensions/writing-standards/screenplay/commercials/CHARACTER-COUNT-TRACKING.md +226 -0
  2. package/augment-extensions/writing-standards/screenplay/commercials/CONFIGURATION.md +327 -0
  3. package/augment-extensions/writing-standards/screenplay/commercials/README.md +223 -0
  4. package/augment-extensions/writing-standards/screenplay/commercials/REFACTORING-SUMMARY.md +193 -0
  5. package/augment-extensions/writing-standards/screenplay/commercials/REFERENCES.md +157 -0
  6. package/augment-extensions/writing-standards/screenplay/commercials/VALIDATION-REPORT.md +109 -0
  7. package/augment-extensions/writing-standards/screenplay/commercials/examples/.gitkeep +0 -0
  8. package/augment-extensions/writing-standards/screenplay/commercials/module.json +111 -0
  9. package/augment-extensions/writing-standards/screenplay/commercials/rules/.gitkeep +0 -0
  10. package/augment-extensions/writing-standards/screenplay/commercials/rules/commercial-audience.md +343 -0
  11. package/augment-extensions/writing-standards/screenplay/commercials/rules/commercial-formats.md +396 -0
  12. package/augment-extensions/writing-standards/screenplay/commercials/rules/commercial-persuasion.md +387 -0
  13. package/augment-extensions/writing-standards/screenplay/commercials/rules/commercial-scripts.md +531 -0
  14. package/augment-extensions/writing-standards/screenplay/commercials/rules/commercial-techniques.md +912 -0
  15. package/augment-extensions/writing-standards/screenplay/commercials/rules/commercial-types.md +700 -0
  16. package/augment-extensions/writing-standards/screenplay/commercials/rules/commercials.md +285 -0
  17. package/augment-extensions/writing-standards/screenplay/commercials/scripts/validate-character-count.ts +284 -0
  18. package/cli/dist/commands/generate-shot-list/generator/ai-blocking-extractor.d.ts +58 -0
  19. package/cli/dist/commands/generate-shot-list/generator/ai-blocking-extractor.d.ts.map +1 -0
  20. package/cli/dist/commands/generate-shot-list/generator/ai-blocking-extractor.js +275 -0
  21. package/cli/dist/commands/generate-shot-list/generator/ai-blocking-extractor.js.map +1 -0
  22. package/cli/dist/commands/generate-shot-list/generator/ai-entity-extractor.d.ts +41 -0
  23. package/cli/dist/commands/generate-shot-list/generator/ai-entity-extractor.d.ts.map +1 -0
  24. package/cli/dist/commands/generate-shot-list/generator/ai-entity-extractor.js +155 -0
  25. package/cli/dist/commands/generate-shot-list/generator/ai-entity-extractor.js.map +1 -0
  26. package/cli/dist/commands/generate-shot-list/generator/context-builder.d.ts +65 -2
  27. package/cli/dist/commands/generate-shot-list/generator/context-builder.d.ts.map +1 -1
  28. package/cli/dist/commands/generate-shot-list/generator/context-builder.js +394 -98
  29. package/cli/dist/commands/generate-shot-list/generator/context-builder.js.map +1 -1
  30. package/cli/dist/commands/generate-shot-list/generator/index.d.ts +59 -1
  31. package/cli/dist/commands/generate-shot-list/generator/index.d.ts.map +1 -1
  32. package/cli/dist/commands/generate-shot-list/generator/index.js +364 -34
  33. package/cli/dist/commands/generate-shot-list/generator/index.js.map +1 -1
  34. package/cli/dist/commands/generate-shot-list/generator/types.d.ts +3 -2
  35. package/cli/dist/commands/generate-shot-list/generator/types.d.ts.map +1 -1
  36. package/cli/dist/commands/generate-shot-list/generator/validator.d.ts +33 -0
  37. package/cli/dist/commands/generate-shot-list/generator/validator.d.ts.map +1 -1
  38. package/cli/dist/commands/generate-shot-list/generator/validator.js +167 -0
  39. package/cli/dist/commands/generate-shot-list/generator/validator.js.map +1 -1
  40. package/cli/dist/commands/generate-shot-list/help-text.d.ts +1 -1
  41. package/cli/dist/commands/generate-shot-list/help-text.d.ts.map +1 -1
  42. package/cli/dist/commands/generate-shot-list/help-text.js +11 -0
  43. package/cli/dist/commands/generate-shot-list/help-text.js.map +1 -1
  44. package/cli/dist/commands/generate-shot-list.d.ts +1 -0
  45. package/cli/dist/commands/generate-shot-list.d.ts.map +1 -1
  46. package/cli/dist/commands/generate-shot-list.js +7 -4
  47. package/cli/dist/commands/generate-shot-list.js.map +1 -1
  48. package/package.json +5 -1
@@ -11,6 +11,7 @@ const validator_1 = require("./validator");
11
11
  const scene_segmenter_1 = require("./scene-segmenter");
12
12
  const context_builder_1 = require("./context-builder");
13
13
  const metadata_extractor_1 = require("./metadata-extractor");
14
+ const ai_blocking_extractor_1 = require("./ai-blocking-extractor");
14
15
  /**
15
16
  * Default generator implementation
16
17
  */
@@ -18,27 +19,37 @@ class ShotListGenerator {
18
19
  constructor(styleGuidelines) {
19
20
  this.validator = (0, validator_1.createValidator)();
20
21
  this.styleGuidelines = null;
22
+ this.characterBlockingCache = new Map();
23
+ // Store style guidelines first
24
+ this.styleGuidelines = styleGuidelines || null;
21
25
  // Initialize modules with default configurations
22
26
  this.segmenter = new scene_segmenter_1.SceneSegmenter({
23
27
  maxShotLength: 30, // 30 seconds max
24
28
  targetShotLength: 15, // 15 seconds target
25
29
  minShotLength: 5 // 5 seconds minimum
26
30
  });
31
+ // Pass style guidelines to ContextBuilder so it can filter appropriately
27
32
  this.contextBuilder = new context_builder_1.ContextBuilder({
28
33
  includeAtmosphere: true,
29
34
  includeWeather: true,
30
- trackCharacterEmotions: true
35
+ trackCharacterEmotions: true,
36
+ styleGuidelines: this.styleGuidelines
31
37
  });
32
38
  this.metadataExtractor = new metadata_extractor_1.MetadataExtractor({
33
39
  extractTechnicalNotes: true,
34
40
  inferFromContext: true
35
41
  });
36
- this.styleGuidelines = styleGuidelines || null;
42
+ this.blockingExtractor = new ai_blocking_extractor_1.AIBlockingExtractor(undefined, this.styleGuidelines);
37
43
  }
38
44
  /**
39
45
  * Generate shot list from parsed screenplay
40
46
  */
41
- generate(scenes, config) {
47
+ async generate(scenes, config) {
48
+ // CRITICAL: Reset all state at the start of each screenplay generation
49
+ // This prevents Character Bible contamination between different screenplays
50
+ this.contextBuilder.reset();
51
+ this.characterBlockingCache.clear();
52
+ this.blockingExtractor.clearCache(); // Clear character description cache
42
53
  const shots = [];
43
54
  let shotNumber = 1;
44
55
  const warnings = [];
@@ -46,7 +57,7 @@ class ShotListGenerator {
46
57
  for (const scene of scenes) {
47
58
  // TODO: Implement scene segmentation (bd-shot-list-3.2)
48
59
  // For now, create one shot per scene as placeholder
49
- const sceneShots = this.segmentScene(scene, shotNumber, config);
60
+ const sceneShots = await this.segmentScene(scene, shotNumber, config);
50
61
  shots.push(...sceneShots);
51
62
  shotNumber += sceneShots.length;
52
63
  }
@@ -96,7 +107,7 @@ class ShotListGenerator {
96
107
  * Implemented: bd-shot-list-3.2
97
108
  * Enhanced: bd-2b68 (Requirement 2: Intelligent Shot Splitting)
98
109
  */
99
- segmentScene(scene, startShotNumber, config) {
110
+ async segmentScene(scene, startShotNumber, config) {
100
111
  // Build scene-level context once
101
112
  const sceneContext = this.contextBuilder.buildSceneContext(scene);
102
113
  // Segment scene into shots
@@ -108,20 +119,30 @@ class ShotListGenerator {
108
119
  const segment = segments[i];
109
120
  const isFirstShot = i === 0;
110
121
  // Build character states for this segment
111
- const characters = this.contextBuilder.buildCharacterStates(segment.elements, sceneContext);
122
+ const characters = await this.contextBuilder.buildCharacterStates(segment.elements, sceneContext);
112
123
  // Extract metadata for this segment
113
124
  const metadata = this.metadataExtractor.extract(segment.elements, isFirstShot);
114
125
  // Apply style guidelines if available
115
126
  if (this.styleGuidelines) {
116
127
  this.applyStyleToMetadata(metadata, this.styleGuidelines);
117
128
  }
129
+ // Extract blocking using AI
130
+ const blockingData = await this.extractBlockingWithAI(segment.elements, characters);
131
+ // Apply AI-generated character descriptions to character states
132
+ this.applyCharacterDescriptions(characters, blockingData.characterDescriptions);
118
133
  // Build structured description components
119
134
  const set = this.buildSetDescription(sceneContext);
120
- const description = this.buildVisualDescription(sceneContext);
121
- const actions = this.buildActions(segment.elements);
135
+ const description = blockingData.setDescription || this.buildVisualDescription(sceneContext);
136
+ const actions = blockingData.characterActions.length > 0
137
+ ? blockingData.characterActions.join('. ')
138
+ : 'No specific actions in this shot';
122
139
  const dialogue = this.extractDialogue(segment.elements);
123
- const blocking = this.buildBlocking(characters);
124
- const sfx = this.buildSFX(segment.elements);
140
+ const blocking = this.buildBlockingFromAI(blockingData.characterPositions, characters);
141
+ const sfx = config.muteSfx
142
+ ? 'No sound effects specified'
143
+ : (blockingData.soundEffects.length > 0
144
+ ? blockingData.soundEffects.join('. ')
145
+ : 'No sound effects specified');
125
146
  const techDetails = this.buildTechDetails(metadata);
126
147
  // Calculate total character count from all components
127
148
  const totalCharacterCount = set.length + description.length + actions.length +
@@ -129,7 +150,7 @@ class ShotListGenerator {
129
150
  // Check if shot exceeds max duration (Requirement 2)
130
151
  if (segment.estimatedDuration > config.maxShotLength) {
131
152
  // Split into sub-shots
132
- const subShots = this.splitIntoSubShots(segment, currentShotNumber, scene, sceneContext, config);
153
+ const subShots = await this.splitIntoSubShots(segment, currentShotNumber, scene, sceneContext, config);
133
154
  shots.push(...subShots);
134
155
  currentShotNumber += subShots.length;
135
156
  }
@@ -163,7 +184,7 @@ class ShotListGenerator {
163
184
  * Requirement 2: Intelligent Shot Splitting for Long Durations
164
185
  * Recursively splits sub-shots that still exceed maxShotLength
165
186
  */
166
- splitIntoSubShots(segment, baseShotNumber, scene, sceneContext, config) {
187
+ async splitIntoSubShots(segment, baseShotNumber, scene, sceneContext, config) {
167
188
  const subShots = [];
168
189
  const elements = segment.elements;
169
190
  const targetDuration = config.maxShotLength;
@@ -180,20 +201,30 @@ class ShotListGenerator {
180
201
  ? `${baseShotNumber}.${i + 1}` // Nested: "3a.1", "3a.2" or "3a.1.1", "3a.1.2"
181
202
  : `${baseShotNumber}${String.fromCharCode(97 + i)}`; // First level: "3a", "3b"
182
203
  // Build character states for this sub-shot
183
- const characters = this.contextBuilder.buildCharacterStates(subShotElements, sceneContext);
204
+ const characters = await this.contextBuilder.buildCharacterStates(subShotElements, sceneContext);
184
205
  // Extract metadata for this sub-shot
185
206
  const metadata = this.metadataExtractor.extract(subShotElements, i === 0);
186
207
  // Apply style guidelines if available
187
208
  if (this.styleGuidelines) {
188
209
  this.applyStyleToMetadata(metadata, this.styleGuidelines);
189
210
  }
211
+ // Extract blocking using AI
212
+ const blockingData = await this.extractBlockingWithAI(subShotElements, characters);
213
+ // Apply AI-generated character descriptions to character states
214
+ this.applyCharacterDescriptions(characters, blockingData.characterDescriptions);
190
215
  // Build structured description components
191
216
  const set = this.buildSetDescription(sceneContext);
192
- const description = this.buildVisualDescription(sceneContext);
193
- const actions = this.buildActions(subShotElements);
217
+ const description = blockingData.setDescription || this.buildVisualDescription(sceneContext);
218
+ const actions = blockingData.characterActions.length > 0
219
+ ? blockingData.characterActions.join('. ')
220
+ : 'No specific actions in this shot';
194
221
  const dialogue = this.extractDialogue(subShotElements);
195
- const blocking = this.buildBlocking(characters);
196
- const sfx = this.buildSFX(subShotElements);
222
+ const blocking = this.buildBlockingFromAI(blockingData.characterPositions, characters);
223
+ const sfx = config.muteSfx
224
+ ? 'No sound effects specified'
225
+ : (blockingData.soundEffects.length > 0
226
+ ? blockingData.soundEffects.join('. ')
227
+ : 'No sound effects specified');
197
228
  const techDetails = this.buildTechDetails(metadata);
198
229
  // Calculate total character count from all components
199
230
  const totalCharacterCount = set.length + description.length + actions.length +
@@ -211,7 +242,7 @@ class ShotListGenerator {
211
242
  breakReason: 'duration'
212
243
  };
213
244
  // Recursively split this sub-shot
214
- const nestedSubShots = this.splitIntoSubShots(nestedSegment, subShotNumber, scene, sceneContext, config);
245
+ const nestedSubShots = await this.splitIntoSubShots(nestedSegment, subShotNumber, scene, sceneContext, config);
215
246
  subShots.push(...nestedSubShots);
216
247
  }
217
248
  // If we have a single dialogue element, split the dialogue text
@@ -225,7 +256,7 @@ class ShotListGenerator {
225
256
  breakReason: 'duration'
226
257
  };
227
258
  // Recursively split the dialogue-based segment
228
- const dialogueSubShots = this.splitIntoSubShots(dialogueSegment, subShotNumber, scene, sceneContext, config);
259
+ const dialogueSubShots = await this.splitIntoSubShots(dialogueSegment, subShotNumber, scene, sceneContext, config);
229
260
  subShots.push(...dialogueSubShots);
230
261
  }
231
262
  // Single non-dialogue element that can't be split further
@@ -412,21 +443,78 @@ class ShotListGenerator {
412
443
  }
413
444
  /**
414
445
  * Build character actions from scene elements
446
+ * Includes both explicit actions and inferred actions from dialogue
415
447
  */
416
448
  buildActions(elements) {
449
+ const actionList = [];
450
+ // Extract explicit action descriptions
451
+ // Include ALL actions (character entrances, movements, and general actions)
452
+ // Only filter out pure sound effects
417
453
  const actionElements = elements.filter(el => el.type === 'action');
418
- if (actionElements.length === 0) {
454
+ const explicitActions = actionElements
455
+ .map(el => el.text)
456
+ .filter(text => {
457
+ // Keep the action if it's NOT a pure sound effect
458
+ // Character entrances should always be included
459
+ return !this.isSoundEffect(text) || this.isCharacterEntrance(text);
460
+ });
461
+ actionList.push(...explicitActions);
462
+ // Infer actions from dialogue (e.g., spell-casting, shouting, etc.)
463
+ const dialogueElements = elements.filter((el) => el.type === 'dialogue');
464
+ for (const dialogueEl of dialogueElements) {
465
+ const inferredAction = this.inferActionFromDialogue(dialogueEl);
466
+ if (inferredAction) {
467
+ actionList.push(inferredAction);
468
+ }
469
+ }
470
+ if (actionList.length === 0) {
419
471
  return 'No specific actions in this shot';
420
472
  }
421
- // Extract action descriptions, filtering out sound effects
422
- const actions = actionElements
423
- .map(el => el.text)
424
- .filter(text => !this.isSoundEffect(text))
425
- .join('. ');
426
- return actions || 'No specific actions in this shot';
473
+ return actionList.join('. ');
474
+ }
475
+ /**
476
+ * Infer character actions from dialogue content
477
+ * Examples:
478
+ * - Spell incantations -> "waves staff and casts spell"
479
+ * - Shouting -> "shouts"
480
+ * - Whispering -> "whispers"
481
+ */
482
+ inferActionFromDialogue(dialogueEl) {
483
+ const characterName = dialogueEl.dialogue.character.name;
484
+ const speech = dialogueEl.dialogue.speech;
485
+ const parenthetical = dialogueEl.dialogue.parenthetical?.toLowerCase() || '';
486
+ // Detect spell incantations - ONLY specific magical phrases
487
+ // Removed overly broad Latin-like pattern that matched treaty names
488
+ const spellPatterns = [
489
+ /\b(Expecto|Lumos|Accio|Wingardium|Expelliarmus|Avada)/i, // Harry Potter spells
490
+ /\b(Abracadabra|Alakazam|Hocus Pocus|Presto)/i, // Classic magic words
491
+ ];
492
+ const isSpell = spellPatterns.some(pattern => pattern.test(speech));
493
+ if (isSpell) {
494
+ // Check if character has a staff/wand in their props
495
+ // For now, assume wizards have staffs/wands
496
+ return `${characterName} waves staff and casts spell`;
497
+ }
498
+ // Detect shouting (ALL CAPS or exclamation marks)
499
+ if (speech === speech.toUpperCase() && speech.length > 10) {
500
+ return `${characterName} shouts`;
501
+ }
502
+ // Detect whispering from parenthetical
503
+ if (parenthetical.includes('whisper')) {
504
+ return `${characterName} whispers`;
505
+ }
506
+ // Detect other emotional delivery
507
+ if (parenthetical.includes('angry') || parenthetical.includes('furious')) {
508
+ return `${characterName} speaks angrily`;
509
+ }
510
+ if (parenthetical.includes('sad') || parenthetical.includes('crying')) {
511
+ return `${characterName} speaks sadly`;
512
+ }
513
+ return null;
427
514
  }
428
515
  /**
429
516
  * Build character blocking (positions and movements)
517
+ * DEPRECATED: Use buildBlockingFromAI() instead
430
518
  */
431
519
  buildBlocking(characters) {
432
520
  if (characters.length === 0) {
@@ -448,19 +536,135 @@ class ShotListGenerator {
448
536
  ? blockingParts.join('. ')
449
537
  : 'Characters maintain their positions';
450
538
  }
539
+ /**
540
+ * Extract blocking using AI-powered spatial inference
541
+ */
542
+ async extractBlockingWithAI(elements, characters) {
543
+ // Extract action lines
544
+ const actionLines = elements
545
+ .filter(el => el.type === 'action')
546
+ .map(el => el.text);
547
+ if (actionLines.length === 0) {
548
+ return {
549
+ characterPositions: [],
550
+ characterDescriptions: [],
551
+ setDescription: '',
552
+ characterActions: [],
553
+ soundEffects: []
554
+ };
555
+ }
556
+ // Get character names
557
+ const characterNames = characters.map(c => c.name);
558
+ // Call AI blocking extractor
559
+ console.log(`Extracting blocking with AI for ${characterNames.length} characters...`);
560
+ const result = await this.blockingExtractor.extractBlocking(actionLines, characterNames, this.characterBlockingCache);
561
+ // Update cache with new positions
562
+ for (const pos of result.characterPositions) {
563
+ this.characterBlockingCache.set(pos.character, pos);
564
+ }
565
+ return result;
566
+ }
567
+ /**
568
+ * Build blocking description from AI-extracted positions
569
+ */
570
+ buildBlockingFromAI(positions, characters) {
571
+ if (positions.length === 0) {
572
+ // Fallback to cached positions
573
+ const cachedBlocking = [];
574
+ for (const char of characters) {
575
+ const cached = this.characterBlockingCache.get(char.name);
576
+ if (cached) {
577
+ cachedBlocking.push(this.formatBlockingPosition(cached));
578
+ }
579
+ }
580
+ return cachedBlocking.length > 0
581
+ ? cachedBlocking.join('\n')
582
+ : 'Characters maintain their positions';
583
+ }
584
+ return positions.map(pos => this.formatBlockingPosition(pos)).join('\n');
585
+ }
586
+ /**
587
+ * Format a single character blocking position
588
+ */
589
+ formatBlockingPosition(pos) {
590
+ const parts = [pos.character + ':'];
591
+ if (pos.action) {
592
+ parts.push(pos.action);
593
+ }
594
+ if (pos.position) {
595
+ parts.push(pos.position);
596
+ }
597
+ if (pos.stagePosition) {
598
+ parts.push(`(${pos.stagePosition})`);
599
+ }
600
+ if (pos.relativePosition) {
601
+ parts.push(`[${pos.relativePosition}]`);
602
+ }
603
+ return parts.join(' ');
604
+ }
605
+ /**
606
+ * Apply AI-generated character descriptions to character states
607
+ */
608
+ applyCharacterDescriptions(characters, descriptions) {
609
+ for (const desc of descriptions) {
610
+ const char = characters.find(c => c.name === desc.character);
611
+ if (char) {
612
+ if (desc.physicalAppearance) {
613
+ char.physicalAppearance = desc.physicalAppearance;
614
+ }
615
+ if (desc.wardrobe) {
616
+ char.wardrobe = desc.wardrobe;
617
+ }
618
+ if (desc.emotion) {
619
+ char.emotion = desc.emotion;
620
+ }
621
+ }
622
+ }
623
+ }
451
624
  /**
452
625
  * Build sound effects from scene elements
626
+ * Filters out character entrances/exits which should be in Actions, not SFX
453
627
  */
454
628
  buildSFX(elements) {
455
- const actionElements = elements.filter(el => el.type === 'action');
629
+ const sfxList = [];
630
+ // Check dialogue for intercom references
631
+ const dialogueElements = elements.filter(el => el.type === 'dialogue');
632
+ for (const dialogueEl of dialogueElements) {
633
+ const parenthetical = dialogueEl.dialogue.parenthetical?.toLowerCase() || '';
634
+ if (parenthetical.includes('intercom') || parenthetical.includes('over intercom')) {
635
+ sfxList.push('Intercom buzz/chirp');
636
+ }
637
+ }
456
638
  // Extract sound effects from action lines
457
- const sfxElements = actionElements
639
+ const actionElements = elements.filter(el => el.type === 'action');
640
+ const actionSfx = actionElements
458
641
  .map(el => el.text)
459
- .filter(text => this.isSoundEffect(text));
460
- if (sfxElements.length === 0) {
642
+ .filter(text => this.isSoundEffect(text) && !this.isCharacterEntrance(text));
643
+ sfxList.push(...actionSfx);
644
+ if (sfxList.length === 0) {
461
645
  return 'No sound effects specified';
462
646
  }
463
- return sfxElements.join('. ');
647
+ return sfxList.join('. ');
648
+ }
649
+ /**
650
+ * Check if text describes a character entrance or exit
651
+ * These should be in Actions, not SFX
652
+ */
653
+ isCharacterEntrance(text) {
654
+ const entranceKeywords = [
655
+ 'enters', 'enter', 'entering',
656
+ 'exits', 'exit', 'exiting',
657
+ 'arrives', 'arrive', 'arriving',
658
+ 'leaves', 'leave', 'leaving',
659
+ 'walks in', 'walks out',
660
+ 'comes in', 'goes out'
661
+ ];
662
+ const lowerText = text.toLowerCase();
663
+ // Check if text contains character introduction pattern: "NAME (description) enters"
664
+ const hasCharacterIntro = /[A-Z][A-Z\s]+\s*\([^)]+\)/.test(text);
665
+ // Check for entrance keywords
666
+ const hasEntranceKeyword = entranceKeywords.some(keyword => lowerText.includes(keyword));
667
+ return hasCharacterIntro || hasEntranceKeyword;
464
668
  }
465
669
  /**
466
670
  * Check if text describes a sound effect
@@ -498,19 +702,35 @@ class ShotListGenerator {
498
702
  * Build verbose context description for generative AI
499
703
  * Target: Extremely detailed set/environment description (1000+ characters)
500
704
  * PRIORITY: Extract concrete details from screenplay, avoid generic filler
705
+ * STYLE-AWARE: Apply style-specific visual characteristics
706
+ * QUALITY: Environment only - no character actions
501
707
  */
502
708
  buildVerboseContext(context) {
503
709
  const parts = [];
504
710
  // Start with the actual set description from the screenplay
505
711
  // This should contain the concrete visual details
506
712
  if (context.description && context.description.length > 50) {
507
- // Use the actual screenplay description as the foundation
508
- parts.push(context.description);
713
+ // Clean the description to remove character actions
714
+ const environmentOnly = this.extractEnvironmentDescription(context.description);
715
+ if (environmentOnly) {
716
+ parts.push(environmentOnly);
717
+ }
718
+ else {
719
+ // Fallback if cleaning removed everything
720
+ parts.push(`${context.set} fills the frame`);
721
+ }
509
722
  }
510
723
  else {
511
724
  // Fallback: Build from available context
512
725
  parts.push(`${context.set} fills the frame`);
513
726
  }
727
+ // Apply style-specific visual characteristics
728
+ if (this.styleGuidelines) {
729
+ const styleEnhancements = this.getStyleVisualCharacteristics(context);
730
+ if (styleEnhancements) {
731
+ parts.push(styleEnhancements);
732
+ }
733
+ }
514
734
  // Add lighting details if specific
515
735
  if (context.lighting && !context.lighting.includes('natural') && !context.lighting.includes('standard')) {
516
736
  parts.push(`Lighting: ${context.lighting}`);
@@ -519,7 +739,7 @@ class ShotListGenerator {
519
739
  if (context.atmosphere && context.atmosphere !== 'neutral') {
520
740
  parts.push(`Atmosphere: ${context.atmosphere}`);
521
741
  }
522
- // Add weather if present
742
+ // Add weather if present (already filtered by ContextBuilder for space settings)
523
743
  if (context.weather) {
524
744
  parts.push(`Weather: ${context.weather}`);
525
745
  }
@@ -533,6 +753,116 @@ class ShotListGenerator {
533
753
  }
534
754
  return parts.join('. ');
535
755
  }
756
+ /**
757
+ * Extract environment description only, removing character actions
758
+ * Keeps: Set details, props, lighting, atmosphere
759
+ * Removes: Character introductions, character actions
760
+ */
761
+ extractEnvironmentDescription(description) {
762
+ // First, remove character introductions with parenthetical descriptions
763
+ // Example: "WIZARD CLIF HIGH (mid-70s, magnificent long white beard...) stands at..."
764
+ // This removes the entire character introduction clause
765
+ let cleaned = description.replace(/[A-Z][A-Z\s]+\([^)]+\)\s+[^.]*\./g, '');
766
+ // Split into sentences to process individually
767
+ const sentences = cleaned.split(/\.\s+/);
768
+ const cleanedSentences = [];
769
+ for (let sentence of sentences) {
770
+ // Skip if this is primarily a character action sentence
771
+ // Check if sentence starts with or contains character action patterns
772
+ if (this.isCharacterActionSentence(sentence)) {
773
+ continue;
774
+ }
775
+ // Trim and check if anything meaningful remains
776
+ sentence = sentence.trim();
777
+ // Keep sentences that have substantial content
778
+ if (sentence.length > 15) {
779
+ cleanedSentences.push(sentence);
780
+ }
781
+ }
782
+ return cleanedSentences.join('. ').trim();
783
+ }
784
+ /**
785
+ * Check if a sentence is primarily about character actions
786
+ * Returns true only if the sentence describes what a character is DOING
787
+ * Returns false if it's describing the environment, even if it contains action-like verbs
788
+ */
789
+ isCharacterActionSentence(sentence) {
790
+ const lowerSentence = sentence.toLowerCase();
791
+ // Check if sentence contains character name patterns (ALL CAPS words or known character names)
792
+ const hasCharacterName = /\b[A-Z]{2,}(?:\s+[A-Z]{2,})*\b/.test(sentence);
793
+ // Character action patterns: "CHARACTER + ACTION VERB"
794
+ // Example: "WIZARD stands at the center", "Clif pauses", "HEIDI enters"
795
+ const characterActionPatterns = [
796
+ /\b(stands?|stood|standing)\s+(at|in|near|by)/i,
797
+ /\b(sits?|sat|sitting)\s+(at|in|on|down)/i,
798
+ /\b(walks?|walked|walking)\s+(to|toward|into|through)/i,
799
+ /\b(enters?|entered|entering)\b/i,
800
+ /\b(exits?|exited|exiting)\b/i,
801
+ /\b(waves?|waved|waving)\s+(his|her|their|a)/i,
802
+ /\b(pauses?|paused|pausing)\b/i,
803
+ /\b(sighs?|sighed|sighing)\b/i,
804
+ /\b(looks?|looked|looking)\s+(at|toward|up|down)/i,
805
+ /\b(turns?|turned|turning)\s+(to|toward|around)/i,
806
+ /\b(moves?|moved|moving)\s+(to|toward|closer)/i,
807
+ /\b(gestures?|gestured|gesturing)\b/i,
808
+ /\b(points?|pointed|pointing)\s+(at|to|toward)/i,
809
+ /\b(reaches?|reached|reaching)\s+(for|toward|out)/i,
810
+ /\b(grabs?|grabbed|grabbing)\b/i
811
+ ];
812
+ // Check if sentence matches character action patterns
813
+ const hasCharacterAction = characterActionPatterns.some(pattern => pattern.test(lowerSentence));
814
+ // Return true only if it has both a character reference AND an action pattern
815
+ return hasCharacterName && hasCharacterAction;
816
+ }
817
+ /**
818
+ * Check if a sentence is a character introduction
819
+ * Example: "WIZARD CLIF HIGH (mid-70s, magnificent long white beard...)"
820
+ */
821
+ isCharacterIntroduction(sentence) {
822
+ // Character introductions typically have:
823
+ // 1. All-caps name
824
+ // 2. Parenthetical description with age, appearance, etc.
825
+ const hasAllCapsName = /[A-Z]{2,}\s+[A-Z]{2,}/.test(sentence);
826
+ const hasParentheticalDescription = /\([^)]*\)/.test(sentence);
827
+ return hasAllCapsName && hasParentheticalDescription;
828
+ }
829
+ /**
830
+ * Get style-specific visual characteristics based on active style guidelines
831
+ * Returns visual descriptors that match the franchise/format aesthetic
832
+ */
833
+ getStyleVisualCharacteristics(context) {
834
+ if (!this.styleGuidelines || !this.styleGuidelines.appliedStyles) {
835
+ return null;
836
+ }
837
+ const characteristics = [];
838
+ const appliedStylesLower = this.styleGuidelines.appliedStyles.map(s => s.toLowerCase());
839
+ // Check for Star Trek franchise style
840
+ if (appliedStylesLower.some(s => s.includes('star trek'))) {
841
+ // Apply Star Trek visual aesthetic
842
+ characteristics.push('Clean futurism with bright, optimistic lighting');
843
+ characteristics.push('Sleek, utopian design with smooth surfaces and advanced technology');
844
+ // Check if this is a bridge/command center
845
+ const location = context.set.toLowerCase();
846
+ if (location.includes('bridge') || location.includes('command')) {
847
+ characteristics.push('Command stations with illuminated displays and ergonomic controls');
848
+ }
849
+ }
850
+ // Check for SNL comedy format style
851
+ if (appliedStylesLower.some(s => s.includes('saturday night live') || s.includes('snl'))) {
852
+ // Apply SNL visual style
853
+ characteristics.push('Exaggerated, absurd visual comedy elements');
854
+ characteristics.push('Fast-paced, chaotic energy with physical comedy potential');
855
+ // Emphasize the comedic tone
856
+ if (context.atmosphere && context.atmosphere !== 'neutral') {
857
+ characteristics.push(`Absurdly ${context.atmosphere} atmosphere for comedic effect`);
858
+ }
859
+ }
860
+ // Check for other franchises
861
+ if (appliedStylesLower.some(s => s.includes('star wars'))) {
862
+ characteristics.push('Lived-in, used-future aesthetic with weathered technology');
863
+ }
864
+ return characteristics.length > 0 ? characteristics.join('. ') : null;
865
+ }
536
866
  /**
537
867
  * Build verbose character description for generative AI
538
868
  * Target: 500-800 characters per character with complete visual detail