param() $ErrorActionPreference = 'Stop' $projectRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path $failures = [System.Collections.Generic.List[string]]::new() function Read-ProjectText([string]$relativePath) { Get-Content -LiteralPath (Join-Path $projectRoot $relativePath) -Raw -Encoding UTF8 } # Validate file destinations, not external URLs or Markdown heading anchors. $linkCount = 0 $markdownFiles = Get-ChildItem -LiteralPath $projectRoot -Filter '*.md' -File -Recurse | Where-Object { $_.FullName -notmatch '[\\/]\.git[\\/]' } foreach ($file in $markdownFiles) { $source = Get-Content -LiteralPath $file.FullName -Raw -Encoding UTF8 foreach ($match in [regex]::Matches($source, '\[[^\]]*\]\(([^)]+)\)')) { $target = $match.Groups[1].Value.Trim().Trim('<', '>') if ($target -match '^(https?://|mailto:|codex:|#)' -or $target -match '[<>]') { continue } $target = [uri]::UnescapeDataString(($target -split '#', 2)[0]) if (-not $target) { continue } $linkCount++ if (-not (Test-Path -LiteralPath (Join-Path $file.DirectoryName $target))) { $failures.Add("Missing link: $($file.FullName) -> $target") } } } $timelineRows = 0 foreach ($line in ((Read-ProjectText '03_故事大纲/主线总年表.md') -split '\r?\n')) { if ($line -match '^\|\s*([+-]?\d+)\s*\|\s*(\d+)\s*\|[^|]+\|\s*(\d+)\s*\|\s*(\d+|未出生)\s*\|') { $timelineRows++ $year = [int]$Matches[2] if ([int]$Matches[1] -ne ($year - 874) -or [int]$Matches[3] -ne ($year - 855)) { $failures.Add("Timeline mismatch: $line") } if ($Matches[4] -ne '未出生' -and [int]$Matches[4] -ne ($year - 856)) { $failures.Add("Hero age mismatch: $line") } } } if ($timelineRows -ne 22) { $failures.Add("Expected 22 timeline rows, got $timelineRows") } $scriptText = Read-ProjectText '06_AI动漫制作/01_改编开发/03_动画剧本/S01E01_停不下来的夜_V01.txt' $sceneIds = @([regex]::Matches($scriptText, '(?m)^(01-\d{2})\s') | ForEach-Object { $_.Groups[1].Value }) $expectedSceneIds = @(1..16 | ForEach-Object { '01-{0:D2}' -f $_ }) if (($sceneIds -join ',') -ne ($expectedSceneIds -join ',')) { $failures.Add('Script scenes must be ordered 01-01 through 01-16') } $outline = Read-ProjectText '06_AI动漫制作/01_改编开发/02_分集大纲/S01E01_分场大纲.md' $sceneRows = @([regex]::Matches($outline, '(?m)^\| (01-\d{2}) \|[^|]+\| (\d+) \|')) $sceneTotal = ($sceneRows | ForEach-Object { [int]$_.Groups[2].Value } | Measure-Object -Sum).Sum if ($sceneTotal -ne 1290 -or (($sceneRows | ForEach-Object { $_.Groups[1].Value }) -join ',') -ne ($expectedSceneIds -join ',')) { $failures.Add('Scene outline IDs or 1290-second budget mismatch') } $shotTable = Read-ProjectText '06_AI动漫制作/03_分镜与镜头/01_镜头表/S01E01_SC12_75秒试片镜头表.md' $motionTable = Read-ProjectText '06_AI动漫制作/04_生成方案/02_视频提示词/S01E01_SC12_运动提示词.md' $shots = @([regex]::Matches($shotTable, '(?m)^\| (SH\d{3}) \| (\d+)/(\d+) \|')) $motion = @([regex]::Matches($motionTable, '(?m)^\| (SH\d{3}) \| (\d+) \|')) $cumulative = 0 if ($shots.Count -ne 12 -or $motion.Count -ne 12) { $failures.Add('Expected 12 shot and motion rows') } for ($index = 0; $index -lt $shots.Count; $index++) { $shot = $shots[$index] $cumulative += [int]$shot.Groups[2].Value if ($shot.Groups[1].Value -ne ('SH{0:D3}' -f ($index + 1)) -or [int]$shot.Groups[3].Value -ne $cumulative) { $failures.Add("Shot sequence or cumulative duration mismatch: $($shot.Value)") } if ($index -lt $motion.Count -and ($motion[$index].Groups[1].Value -ne $shot.Groups[1].Value -or $motion[$index].Groups[2].Value -ne $shot.Groups[2].Value)) { $failures.Add("Motion mismatch at shot $($index + 1)") } } if ($cumulative -ne 75) { $failures.Add("Expected 75 seconds, got $cumulative") } $bodyStart = $scriptText.IndexOf('01-01 ') $scriptChars = if ($bodyStart -ge 0) { [regex]::Replace($scriptText.Substring($bodyStart), '\s', '').Length } else { 0 } $novelFiles = Get-ChildItem -LiteralPath (Join-Path $projectRoot '05_小说创作/03_正文/第一卷') -Filter '*.md' -File | Where-Object { $_.Name -match '^\d{2}_' } $novelChars = ($novelFiles | ForEach-Object { [regex]::Replace((Get-Content -LiteralPath $_.FullName -Raw -Encoding UTF8), '\s', '').Length } | Measure-Object -Sum).Sum $pngCount = @(Get-ChildItem -LiteralPath (Join-Path $projectRoot '06_AI动漫制作') -Filter '*.png' -File -Recurse).Count Write-Output "Links checked: $linkCount; timeline rows: $timelineRows; scenes: $($sceneIds.Count); scene budget: $sceneTotal sec" Write-Output "Shots: $($shots.Count); shot budget: $cumulative sec; script body non-whitespace chars: $scriptChars" Write-Output "Novel draft files: $($novelFiles.Count); draft non-whitespace chars including headings: $novelChars; PNG references: $pngCount" if ($failures.Count -gt 0) { $failures | ForEach-Object { Write-Output "FAIL: $_" } exit 1 } Write-Output 'PASS. This checks document structure, not artistic quality, medical facts, actual runtime or production readiness.' exit 0