From 5686ebd6f204452df648863efd03f82f9d8b8b3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=B7=E9=BE=99?= Date: Wed, 6 Aug 2025 18:53:55 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=A4=9A=E4=BD=99=E5=86=85?= =?UTF-8?q?=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/service/Interaction/ScriptService.ts | 305 ++++++++++------------- app/service/test/testScript.txt | 1 - 2 files changed, 135 insertions(+), 171 deletions(-) delete mode 100644 app/service/test/testScript.txt diff --git a/app/service/Interaction/ScriptService.ts b/app/service/Interaction/ScriptService.ts index 6868dcf..8dd0a08 100644 --- a/app/service/Interaction/ScriptService.ts +++ b/app/service/Interaction/ScriptService.ts @@ -1,8 +1,6 @@ -import { useState, useCallback, useMemo } from "react"; -import { ScriptSlice } from "../domain/valueObject"; +import { useState, useCallback } from "react"; import { ScriptEditUseCase } from "../usecase/ScriptEditUseCase"; -import { getProjectScript, saveScript as saveScriptAPI, createProject as createProjectAPI } from "../../../api/video_flow"; -import { throttle } from "@/utils/tools"; +import { getProjectScript, abortVideoTask } from "../../../api/video_flow"; /** * 剧本服务Hook接口 @@ -10,170 +8,179 @@ import { throttle } from "@/utils/tools"; */ export interface UseScriptService { // 响应式状态 - /** 当前剧本文本 */ - scriptText: string; - /** 剧本片段列表 */ - scriptSlices: ScriptSlice[]; - /** 用户提示词(可编辑) */ - userPrompt: string; /** 加载状态 */ loading: boolean; + /** 故事梗概 */ + synopsis: string; + /** 故事分类 */ + categories: string[]; + /** 主角名称 */ + protagonist: string; + /** 激励事件 */ + incitingIncident: string; + /** 问题与新目标 */ + problem: string; + /** 冲突与障碍 */ + conflict: string; + /** 赌注 */ + stakes: string; + /** 人物弧线完成 */ + characterArc: string; /** 项目ID */ projectId: string; + /** 计划ID */ + planId: string; // 操作方法 - /** 获取根据用户想法调用接口AI生成剧本(用户提示词) */ - fetchScriptData: (prompt: string) => Promise; - /** 根据项目ID获取已存在的剧本数据 */ - fetchProjectScript: (projectId: string) => Promise; - /** 更新用户提示词 */ - updateUserPrompt: (prompt: string) => void; - /** 重置剧本内容到初始状态 */ - resetScript: () => void; - /** 应用剧本 */ + /** 根据用户想法生成剧本并自动创建项目 */ + generateScriptFromIdea: (idea: string) => Promise; + /** 根据项目ID初始化已有剧本 */ + initializeFromProject: (projectId: string) => Promise; + /** 修改剧本 */ + updateScript: (scriptText: string) => void; + /** 应用剧本到视频生成流程 */ applyScript: () => Promise; - /** 中断剧本生成 */ - abortGenerateScript: () => void; - /** 保存剧本 */ - saveScript: () => Promise; - /** 创建项目 */ - createProject: () => Promise; + /** 中断视频任务 */ + abortVideoTask: () => Promise; + } /** * 剧本服务Hook * 提供剧本相关的所有状态管理和操作方法 - * 包括剧本数据获取、片段管理等功能 + * 包括剧本生成、项目创建、剧本保存等功能 */ export const useScriptService = (): UseScriptService => { // 响应式状态 - const [scriptText, setScriptText] = useState(""); - const [scriptSlices, setScriptSlices] = useState([]); - const [userPrompt, setUserPrompt] = useState(""); - const [initialScriptText, setInitialScriptText] = useState(""); const [loading, setLoading] = useState(false); + const [synopsis, setSynopsis] = useState(""); + const [categories, setCategories] = useState([]); + const [protagonist, setProtagonist] = useState(""); + const [incitingIncident, setIncitingIncident] = useState(""); + const [problem, setProblem] = useState(""); + const [conflict, setConflict] = useState(""); + const [stakes, setStakes] = useState(""); + const [characterArc, setCharacterArc] = useState(""); const [projectId, setProjectId] = useState(""); + const [planId, setPlanId] = useState(""); + // UseCase实例 const [scriptEditUseCase, setScriptEditUseCase] = useState(null); /** - * 初始化,ai生成剧本(用户提示词) - * @param prompt 用户提示词 + * 根据用户想法生成剧本并自动创建项目 + * @param idea 用户想法 */ - const fetchScriptData = useCallback(async (prompt: string): Promise => { + const generateScriptFromIdea = useCallback(async (idea: string): Promise => { try { setLoading(true); - // 清空当前状态 - setScriptText(""); - setScriptSlices([]); - - // 更新用户提示词状态 - setUserPrompt(prompt); // 创建新的剧本编辑用例 const newScriptEditUseCase = new ScriptEditUseCase(''); setScriptEditUseCase(newScriptEditUseCase); // 调用AI生成剧本 - await newScriptEditUseCase.generateScript(prompt,throttle((newContent)=>{ - // 获取生成的剧本文本 - const generatedScriptText = newScriptEditUseCase.toString(); - setScriptText(generatedScriptText); - console.log(scriptText); + await newScriptEditUseCase.generateScript(idea, (content) => { + // 获取解析后的故事详情 + const storyDetails = newScriptEditUseCase.getStoryDetails(); + setSynopsis(storyDetails.synopsis || ""); + setCategories(storyDetails.categories || []); + setProtagonist(storyDetails.protagonist || ""); + setIncitingIncident(storyDetails.incitingIncident || ""); + setProblem(storyDetails.problem || ""); + setConflict(storyDetails.conflict || ""); + setStakes(storyDetails.stakes || ""); + setCharacterArc(storyDetails.characterArc || ""); + }); - // 获取剧本片段列表 - const slices = newScriptEditUseCase.getScriptSlices(); - setScriptSlices(slices); + // 剧本生成完成后,自动创建项目 + const projectData = await newScriptEditUseCase.createProject( + idea, + "user123", + "auto", + "720p" + ); - // 保存初始剧本文本(只在第一次获取时保存) - if (!initialScriptText) { - setInitialScriptText(generatedScriptText); - } - })); + setProjectId(projectData.project_id); + setPlanId(projectData.plan_id); + + // 自动保存剧本到项目 + await newScriptEditUseCase.saveScript(projectData.project_id); } catch (error) { - console.error('获取剧本数据失败:', error); + console.error('生成剧本失败:', error); throw error; } finally { setLoading(false); } - }, [initialScriptText]); + }, []); /** - * 根据项目ID获取已存在的剧本数据 + * 根据项目ID初始化已有剧本 * @param projectId 项目ID */ - const fetchProjectScript = useCallback(async (projectId: string): Promise => { + const initializeFromProject = useCallback(async (projectId: string): Promise => { try { setLoading(true); - // 清空当前状态 - setScriptText(""); - setScriptSlices([]); // 设置项目ID setProjectId(projectId); // 调用API获取项目剧本数据 - const response = await getProjectScript({ projectId }); + const response = await getProjectScript({ project_id: projectId }); if (!response.successful) { throw new Error(response.message || '获取项目剧本失败'); } - const { prompt, scriptText } = response.data; - - // 更新用户提示词状态 - setUserPrompt(prompt); - - // 保存初始剧本文本(只在第一次获取时保存) - if (!initialScriptText) { - setInitialScriptText(scriptText); - } + const { generated_script } = response.data; // 创建新的剧本编辑用例并初始化数据 - const newScriptEditUseCase = new ScriptEditUseCase(scriptText); + const newScriptEditUseCase = new ScriptEditUseCase(generated_script); setScriptEditUseCase(newScriptEditUseCase); - // 设置剧本文本 - setScriptText(scriptText); - - // 从UseCase获取解析后的剧本片段 - const scriptSlices = newScriptEditUseCase.getScriptSlices(); - setScriptSlices(scriptSlices); + // 获取解析后的故事详情 + const storyDetails = newScriptEditUseCase.getStoryDetails(); + setSynopsis(storyDetails.synopsis || ""); + setCategories(storyDetails.categories || []); + setProtagonist(storyDetails.protagonist || ""); + setIncitingIncident(storyDetails.incitingIncident || ""); + setProblem(storyDetails.problem || ""); + setConflict(storyDetails.conflict || ""); + setStakes(storyDetails.stakes || ""); + setCharacterArc(storyDetails.characterArc || ""); } catch (error) { - console.error('获取项目剧本数据失败:', error); + console.error('初始化项目剧本失败:', error); throw error; } finally { setLoading(false); } - }, [initialScriptText]); - - /** - * 更新用户提示词 - * @param prompt 新的用户提示词 - */ - const updateUserPrompt = useCallback((prompt: string): void => { - setUserPrompt(prompt); }, []); /** - * 重置剧本内容到初始状态 + * 修改剧本 + * @param scriptText 新的剧本文本 */ - const resetScript = useCallback((): void => { - if (initialScriptText && scriptEditUseCase) { - // 重置剧本文本到初始状态 - setScriptText(initialScriptText); - // 更新现有剧本编辑用例的数据 - scriptEditUseCase.updateScript(initialScriptText); - // 从UseCase获取解析后的剧本片段 - const scriptSlices = scriptEditUseCase.getScriptSlices(); - setScriptSlices(scriptSlices); + const updateScript = useCallback((scriptText: string): void => { + if (scriptEditUseCase) { + scriptEditUseCase.updateScript(scriptText); + + // 更新解析后的故事详情 + const storyDetails = scriptEditUseCase.getStoryDetails(); + setSynopsis(storyDetails.synopsis || ""); + setCategories(storyDetails.categories || []); + setProtagonist(storyDetails.protagonist || ""); + setIncitingIncident(storyDetails.incitingIncident || ""); + setProblem(storyDetails.problem || ""); + setConflict(storyDetails.conflict || ""); + setStakes(storyDetails.stakes || ""); + setCharacterArc(storyDetails.characterArc || ""); } - }, [initialScriptText, scriptEditUseCase]); + }, [scriptEditUseCase]); /** - * 应用剧本 + * 应用剧本到视频生成流程 */ const applyScript = useCallback(async (): Promise => { try { @@ -183,7 +190,11 @@ export const useScriptService = (): UseScriptService => { throw new Error("剧本编辑用例未初始化"); } - await scriptEditUseCase.applyScript(projectId); + if (!projectId || !planId) { + throw new Error("项目ID或计划ID未设置"); + } + + await scriptEditUseCase.applyScript(projectId, planId); } catch (error) { console.error("应用剧本失败:", error); @@ -191,101 +202,55 @@ export const useScriptService = (): UseScriptService => { } finally { setLoading(false); } - }, [scriptEditUseCase, projectId]); + }, [scriptEditUseCase, projectId, planId]); /** - * 中断剧本生成 + * 中断视频任务 */ - const abortGenerateScript = useCallback((): void => { - if (scriptEditUseCase) { - scriptEditUseCase.abortGenerateScript(); - setLoading(false); - } - }, [scriptEditUseCase]); - - /** - * 保存剧本 - */ - const saveScript = useCallback(async (): Promise => { + const abortVideoTaskHandler = useCallback(async (): Promise => { try { - setLoading(true); - if (!projectId) { - throw new Error("项目ID未设置"); + if (!projectId || !planId) { + throw new Error("项目ID或计划ID未设置"); } - if (!scriptEditUseCase) { - throw new Error("剧本编辑用例未初始化"); - } - - // 调用保存剧本接口 - const scriptText = scriptEditUseCase.toString(); - const response = await saveScriptAPI({ projectId, scriptText }); - - if (!response.successful) { - throw new Error(response.message || '保存剧本失败'); - } - - console.log("剧本保存成功"); - - } catch (error) { - console.error("保存剧本失败:", error); - throw error; - } finally { - setLoading(false); - } - }, [projectId, scriptEditUseCase]); - - /** - * 创建项目 - * @throws {Error} - 创建项目失败时抛出异常 - */ - const createProject = useCallback(async (): Promise => { - try { - setLoading(true); - - // 直接使用当前state中的userPrompt和scriptText - const currentUserPrompt = userPrompt; - const currentScriptContent = scriptText; - - const response = await createProjectAPI({ - userPrompt: currentUserPrompt, - scriptContent: currentScriptContent + // 调用中断视频任务API + const response = await abortVideoTask({ + project_id: projectId, + plan_id: planId }); if (!response.successful) { - throw new Error(response.message || '创建项目失败'); + throw new Error(response.message || "中断视频任务失败"); } - const { projectId: newProjectId } = response.data; - setProjectId(newProjectId); - - console.log("项目创建成功"); + console.log("视频任务中断成功"); } catch (error) { - console.error("创建项目失败:", error); + console.error("中断视频任务失败:", error); throw error; - } finally { - setLoading(false); } - }, [userPrompt, scriptText]); + }, [projectId, planId]); return { // 响应式状态 - scriptText, - scriptSlices, - userPrompt, loading, + synopsis, + categories, + protagonist, + incitingIncident, + problem, + conflict, + stakes, + characterArc, projectId, + planId, // 操作方法 - fetchScriptData, - fetchProjectScript, - updateUserPrompt, - resetScript, + generateScriptFromIdea, + initializeFromProject, + updateScript, applyScript, - abortGenerateScript, - saveScript, - createProject, + abortVideoTask: abortVideoTaskHandler, }; }; diff --git a/app/service/test/testScript.txt b/app/service/test/testScript.txt deleted file mode 100644 index aefdc86..0000000 --- a/app/service/test/testScript.txt +++ /dev/null @@ -1 +0,0 @@ -**Core Elements**\n\n1. **Protagonist:**\n * **Core Identity:** AKIO, a young, spirited male Shiba Inu. His fur is a perfect toasted sesame color. He is naive, driven by simple impulses, and possesses an almost comical level of competitive spirit. He lives a comfortable life near a bustling seaside pier in the present day.\n * **Initial State & Flaw:** Akio begins the story content and carefree, enjoying the sun. His critical flaw is a combination of gluttony and a naive obsession with winning. He believes victory, in any form, is the ultimate measure of happiness and worth, failing to see the emptiness of a prize won without purpose.\n\n2. **The Inciting Incident:**\n * A large, forgotten plate of golden french fries is left on a low crate on the pier. His pier rival, HANA, a sleek red Shiba, spots it at the same moment. An unspoken challenge flashes between them. QUEENIE, an elegant Siamese cat who rules the pier, leaps atop a tall piling, implicitly agreeing to officiate. The contest is suddenly, irrevocably on. This event shatters Akio's peaceful morning and directly triggers his competitive flaw.\n\n3. **The Problem & New Goal:**\n * The problem is brutally simple: How can I eat more fries than Hana and win this contest? This gives Akio a new, all-consuming goal: to achieve victory and be crowned the undisputed champion of the fries.\n\n4. **Conflict & Obstacles:**\n * **Primary Conflict:** Akio vs. Hana in a direct, head-to-head eating competition. Internally, Akio battles his growing physical misery against his desperate, ingrained need to win.\n * **Key Obstacles:**\n 1. Hana is an equally matched and fiercely determined opponent.\n 2. The sheer volume of fries turns the contest from a sprint into a grueling marathon, pushing them both to their physical limits.\n\n5. **The Stakes:**\n * **What is at stake?:** If Akio wins, he gains temporary glory and the satisfaction of his ego. If he fails, he loses face to his rival. However, the true stake is what he stands to permanently lose if he *succeeds* through pure gluttony: he will lose the simple joy of food, his physical well-being, and the potential for a genuine connection with Hana, leaving him bloated and alone with a hollow victory.\n\n6. **Character Arc Accomplished:**\n * By the end, faced with the final fry, Akio overcomes his flaw. He sees the contest's absurdity and Hana's shared suffering. He chooses connection over competition, transforming from a mindless glutton into a creature capable of empathy and grace. He learns that sharing a moment is more fulfilling than winning a prize.\n\n**GENRE:** Sports\n\n---\n\n**SCENE 1**\n\n**[SCENE'S CORE CONFLICT]: The thrill of the challenge and the start of an epic, absurd competition.**\n\nEXT. PIER - DAY\n\n**Scene Transition:** The scene opens on a perfect, sun-drenched morning, establishing the peaceful status quo that is about to be shattered.\n\nThe air is salty and bright. Wooden planks, bleached by the sun, stretch out over gently lapping water. AKIO, a handsome Shiba Inu, basks in a patch of sun, tail curled, eyes blissfully shut.\n\nA WIDE SHOT establishes the key players in their starting positions. A few yards away, HANA, a fiery red Shiba, watches a seagull. On a high wooden piling, QUEENIE, a regal Siamese cat, grooms a paw with aristocratic disdain. On a nearby railing, two PIGEONS, GUS and GERTIE, coo softly.\n\nA careless tourist leaves a large paper plate piled high with golden, glistening FRENCH FRIES on a low wooden crate.\n\nCLOSE-UP on Akio's nose twitching. His eyes snap open. At the same instant, Hana turns. Their eyes lock. The air crackles.\n\nQueenie stops grooming. She stares down at them, then at the fries. With a deliberate, fluid motion, she sits perfectly upright, tail wrapped around her paws. The judge has taken her seat.\n\nAkio's tail begins to thump against the wood. A low, excited growl rumbles in his chest. Hana answers with a sharp, challenging yip.\n\n**AKIO (V.O.)**\nThis was it. The big one.\nGlory was on that plate.\n\nWithout another sound, they both lunge.\n\nRAPID CUTS between Akio and Hana gobbling fries. Their muzzles are a blur of motion. Golden sticks disappear. Tails wag like furious metronomes. The pigeons lean forward, heads cocked, their cooing becoming more rapid and excited.\n\n**GUS & GERTIE**\n(Excited cooing)\n(He's fast! But she's relentless!)\n\nQueenie watches, impassive. She lifts a single paw, as if about to signal a foul, then slowly lowers it. She will allow it. For now.\n\n**AKIO (V.O.)**\nShe was good. Real good.\nBut I was born for this.\n\n---\n\n**SCENE 2**\n\n**[SCENE'S CORE CONFLICT]: The escalation of the contest into an absurd, grueling marathon.**\n\nEXT. PIER - MIDDAY\n\n**Scene Transition:** A time-lapse montage shows the sun climbing higher in the sky. The shadows on the pier shrink. The pile of fries on the plate is noticeably smaller, but a new, larger pile of discarded, half-eaten fries is growing on the planks around it. The transition emphasizes the passage of time and the sheer endurance of the contest.\n\nThe midday sun beats down. The initial frenzy has subsided into a grim, rhythmic battle of attrition. Akio and Hana are no longer gobbling; they are CHEWING. Methodically. Grimly. Their faces are greasy. Their bellies are visibly distended.\n\n**AKIO (V.O.)**\nThe thrill was gone.\nNow, it was just a job.\nOne fry. Then the next.\n\nHana pauses, breathing heavily. A single fry hangs from her mouth. She looks at Akio. There is no fire in her eyes now, only exhaustion. Akio meets her gaze, his own jaw working slowly.\n\nQueenie yawns, a flash of pink mouth and sharp teeth. She looks utterly bored. She stretches one leg, then the other, before settling back into her judicial pose.\n\n**QUEENIE**\n(A low, bored meow)\n(Are they still going? Honestly.)\n\nGus and Gertie are still rapt. They hop from one foot to the other, nudging each other, offering quiet commentary.\n\n**GUS & GERTIE**\n(Murmuring coos)\n(His form is slipping. Look.)\n(She has more stamina. Classic.)\n\nAkio swallows with a gulp. He eyes the remaining pile. It still looks like a mountain. He glances at Hana. She takes a deep breath and doggedly chomps another fry.\n\n**AKIO (V.O.)**\nI couldn't quit. Not now.\nChampions don't quit. Right?\n\nHe forces another fry into his mouth. It tastes like cardboard and regret.\n\n---\n\n**SCENE 3**\n\n**[SCENE'S CORE CONFLICT]: The physical and emotional breaking point, where the cost of victory becomes clear.**\n\nEXT. PIER - SUNSET\n\n**Scene Transition:** The scene opens on an empty shot of the sun, a brilliant orange orb, touching the horizon. The water is a sheet of molten gold. The light is warm but fading, casting long, dramatic shadows. This transition marks the end of the day and the climax of the struggle.\n\nThe pier is bathed in the golden hour's glow. The scene is quiet, the earlier energy completely gone. Akio and Hana are lying on the planks, flanking the plate. They are panting, their sides heaving. The mountain of fries is gone.\n\nOnly ONE. SINGLE. FRY remains.\n\nIt sits perfectly in the center of the greasy plate, a final, golden trophy.\n\nAkio lifts his head. It feels like it weighs a thousand pounds. He looks at the fry, then at Hana. She is a mirror of his own misery. Her fur is matted with grease, her eyes are glassy. A tiny, pathetic whimper escapes her.\n\n**AKIO (V.O.)**\nWe had done it. We ate it all.\nBut there was no cheering.\nJust... this. This quiet ache.\n\nQueenie looks down, a flicker of something—pity? annoyance?—in her blue eyes. She lets out a soft, exasperated sigh.\n\n**QUEENIE**\n(A soft, tired meow)\n(Oh, for heaven's sake. Finish it.)\n\nAkio summons the last of his strength. He begins to drag himself towards the plate. Every muscle screams. This is the final push. The winning point. He can taste victory. It tastes like salt and exhaustion.\n\nHe reaches the plate, his nose inches from the final fry.\n\n---\n\n**SCENE 4**\n\n**[SCENE'S CORE CONFLICT]: The resolution through an act of grace, redefining victory as connection.**\n\nEXT. PIER - DUSK\n\n**Scene Transition:** Continuing directly from the previous scene's climax. The final rays of sunlight disappear, and the cool, soft light of dusk settles over the pier. The shift in light mirrors Akio's internal shift from the fiery heat of competition to the cool clarity of realization.\n\nAkio stares at the last fry. The golden light has faded, and the fry looks pale and unappetizing under the blue twilight.\n\nCLOSE-UP on Akio's eyes. We see the reflection of the lonely fry. Then his eyes shift, looking past it, towards Hana. She hasn't moved. She just lies there, defeated and miserable.\n\n**AKIO (V.O.)**\nAnd then I saw it.\nWinning meant she lost.\nWhat kind of prize was that?\n\nA profound change comes over Akio's face. The grim determination melts away, replaced by a soft, clear understanding.\n\nSLOW MOTION as Akio gently nudges the final fry with his nose. He doesn't eat it. He pushes it, slowly, deliberately, across the greasy plate until it stops directly in front of Hana's nose.\n\nHana's eyes flutter open. She looks at the fry, then at Akio. Confusion, then dawning surprise.\n\nAkio gives a small, tired tail wag. A real one. Not the frantic wag of competition, but a gentle wave of peace.\n\nHana looks at the fry for a long moment. Then she ignores it completely. She inches forward and gently licks Akio's nose.\n\nOn the piling, Queenie watches this. For the first time, a genuine, soft smile seems to grace her feline features. She lets out a quiet, approving purr.\n\n**QUEENIE**\n(A soft, rumbling purr)\n(Finally.)\n\nGus and Gertie coo softly, a gentle, contented sound. They bob their heads in unison, as if applauding.\n\nFADE OUT on the two Shibas, lying side-by-side in the twilight, the single, uneaten fry sitting between them like a forgotten trophy.\n\n**AKIO (V.O.)**\nWe didn't have a winner.\nWe had something better.\n\n---\n\n### Part 2: Addendum for Director & Performance\n\n**【Decoding the Directing Style】**\n\n* **Core Visual Tone:** A mock-epic \"sports documentary\" style. Use grand, sweeping shots in the beginning (like an NFL Films production) contrasted with gritty, handheld close-ups during the \"mid-game\" struggle. The color palette should shift dramatically: vibrant, saturated colors in Scene 1; a harsh, overexposed look in Scene 2; a warm, elegiac \"magic hour\" glow in Scene 3; and a cool, peaceful blue/purple palette for the resolution in Scene 4.\n* **Key Scene Treatment Suggestion:** For the climax in Scene 3, when only one fry remains, the sound design should drop out almost completely. All we hear is the labored breathing of the dogs, the gentle lapping of water, and the distant cry of a gull. The camera should be at a very low angle, making the single fry on the plate look like a monumental obelisk. The final action in Scene 4—Akio pushing the fry—should be captured in a single, unbroken take, focusing on the slow, deliberate movement and Hana's dawning reaction.\n\n**【The Core Performance Key】**\n\n* **The Character's Physicality:** Akio's physicality must arc. He starts with a bouncy, \"on the balls of his feet\" energy. In Scene 2, his movements become heavy and sluggish, his chewing laborious. By Scene 3, he should be practically immobile, every movement an immense effort. His final act of pushing the fry should be gentle and tender, a stark contrast to the frantic gobbling at the start.\n* **Subtextual Drive:** Akio's internal voiceover is the text; the subtext is in his eyes and body. His V.O. in Scene 2 says \"Champions don't quit,\" but his eyes should scream, \"I want my mommy.\" The subtext of the final scene is a silent apology and an offering of peace. When Hana licks his nose, the subtext is \"I accept. We're okay.\"\n\n**【Connection to the Zeitgeist】**\n\n* This story of a pointless, all-consuming competition for a meaningless prize serves as a gentle parable for the modern obsession with \"winning\" on social media and in hustle culture, suggesting that true fulfillment is found not in victory, but in shared humanity and connection.