HyperFrames Handbook
ทำวิดีโอจาก HTML ที่แก้ได้เหมือนโค้ด — เข้าใจกลไกเฟรมคือฟังก์ชันของเวลา จนเขียน ตรวจ และเรนเดอร์วิดีโอเองได้ทั้งวงจร
คู่มือที่พาคุณจากคนไม่เคยทำวิดีโอด้วยโค้ด ไปจนเขียน ตรวจ และเรนเดอร์วิดีโอจากเว็บเพจได้เองครบวงจร — เน้นกลไกแกนที่ทำให้กฎทุกข้อของ framework กลายเป็นเรื่อง "เดาได้" ไม่ใช่ท่องจำ
- ระดับ: เริ่มจากศูนย์ (ขอแค่เคยแตะ HTML/CSS) → ใช้งานจริง
- เวลาอ่าน: ~60 นาที
- ภาษา: ไทย พร้อมไดอะแกรม
- ทดสอบกับ: hyperframes v0.8.8 บน Windows 11 — ทุก output ในเล่มรันจริง
LEVEL 0 · ปูพื้นจากศูนย์
ทำไมวิดีโอถึงเป็นเว็บเพจได้ — แล้วทำไมควรเป็น#
ลองนึกถึงวันที่ตัดคลิปแนะนำทีมเสร็จ export รอครึ่งชั่วโมง ส่งเข้ากลุ่มไปแล้ว เพิ่งเห็นว่าสะกดชื่อหัวหน้าทีมผิดหนึ่งตัวอักษร ในโปรแกรมตัดต่อ การแก้ตัวอักษรตัวเดียวแปลว่าเปิดโปรเจกต์ ไล่หากล่องข้อความบน timeline แก้คำ แล้ว export ใหม่ทั้งไฟล์อีกรอบ
ถ้าเป็นเอกสาร คุณแก้คำแล้วกดเซฟ จบในสิบวินาที ที่วิดีโอทำแบบนั้นไม่ได้เพราะไฟล์ .mp4 เก็บเฉพาะผลลัพธ์ปลายทาง คือพิกเซลนับล้านจุด ส่วนต้นทางที่มนุษย์แก้เป็น — ข้อความ สี ตำแหน่ง จังหวะ — ถูกขังไว้ในไฟล์โปรเจกต์ของโปรแกรมตัดต่อ ที่เปิดได้ด้วยโปรแกรมเดียวและเทียบความต่างระหว่างเวอร์ชันไม่ได้เลย
HyperFrames แก้ที่ต้นเหตุ: เก็บต้นทางเป็นเว็บเพจ ตัวหนังสือเป็น text จริงใน HTML สีและตำแหน่งเป็น CSS จังหวะเวลาเป็น attribute ชื่อ data-* แล้วให้เครื่องเรนเดอร์ผลิตไฟล์วิดีโอจากต้นทางนี้ทุกครั้งที่สั่ง การแก้ชื่อที่สะกดผิดจึงเหลือแค่แก้ text หนึ่งคำ สั่งเรนเดอร์ใหม่ จบ
เมื่อวิดีโอทั้งตัวเป็นไฟล์ text สามอย่างที่โปรแกรมตัดต่อให้ไม่ได้ก็ตามมาทันที: เก็บลง Git แล้ว diff ดูได้ว่าเวอร์ชันนี้ต่างจากเมื่อวานตรงไหน · ผลิตซ้ำเป็นระบบได้ เช่นคลิปโครงเดียวกันร้อยชื่อร้อยเวอร์ชัน (บทที่ 12) · และ AI อ่าน แก้ หรือเขียนให้ได้ทั้งตัว เพราะทุกอย่างคือโค้ดที่มันถนัดที่สุดอยู่แล้ว
1.1 สองวิธีใช้ — เขียนเอง กับ กำกับ AI
HyperFrames ถูกออกแบบมาให้ AI ทำงานด้วยตั้งแต่แรก ตัว framework แถมชุดคู่มือสำหรับ AI agent มาในตัว และในงานจริงคุณจะสั่ง AI เขียน composition บ่อยกว่าพิมพ์เองทีละบรรทัด
สองทางนี้ไม่ได้แข่งกัน คนที่กำกับ AI ได้ดีคือคนที่อ่านงานของเครื่องออก — รู้ว่ารายงานตรวจกำลังฟ้องอะไร ดูเฟรมแล้วบอกได้ว่าผิดเพราะกลไกข้อไหน เล่มนี้จึงสอนชั้นกลไก: เข้าใจแล้วจะลงมือเขียนเองก็ได้ หรือสั่งให้คนอื่นเขียนแล้วตรวจรับเป็น ก็ได้ทั้งคู่
1.2 ติดตั้งและตรวจเครื่อง
ของที่ต้องมีในเครื่องมีสองอย่าง: Node.js 22 ขึ้นไป และ FFmpeg จากนั้นตรวจความพร้อมทั้งหมดด้วยคำสั่งเดียว:
$ npx hyperframes doctorเครื่องที่ใช้เขียนเล่มนี้ตอบกลับมาว่า FFmpeg 8.0 พร้อม FFprobe พร้อม และ Chrome headless พร้อม — ตัวสุดท้ายคือเบราว์เซอร์ไร้หน้าจอที่ HyperFrames ใช้ถ่ายเฟรม ดาวน์โหลดให้อัตโนมัติในการใช้งานครั้งแรก ไม่ต้องติดตั้งเอง
1.3 เปิดโปรเจกต์แรก
$ npx hyperframes init my-first-video --example blankได้โฟลเดอร์ที่มีไฟล์สำคัญไฟล์เดียวคือ index.html ที่เหลือเป็นของประกอบ (hyperframes.json ตั้งค่าโปรเจกต์ package.json เก็บทางลัดคำสั่ง) เว็บเพจไฟล์นี้แหละคือวิดีโอทั้งตัว บทที่ 3 จะเปิดผ่าดูทีละบรรทัด
ส่วนปลายทางของเล่ม ขอสปอยล์ให้เห็นก่อน: การ์ดนัดประชุมทีมยาว 10 วินาที ที่คุณจะสร้างเองครบวงจรในบท 3–6 เครื่องที่เขียนเล่มนี้เรนเดอร์มันด้วยคำสั่งเดียว ได้ผลจริงแบบนี้:
◇ renders\handbook-lab_2026-08-22_16-48-08.mp4
326.3 KB · 10.0s video · rendered in 59.3sวิดีโอ 10 วินาที 300 เฟรม เสร็จในหนึ่งนาที และไม่ว่าจะกลับมาแก้คำผิดอีกกี่รอบ ก็จ่ายเวลาเท่านี้เสมอ
เฟรมคือฟังก์ชันของเวลา#
บทนี้สำคัญที่สุดในเล่ม แนวคิดเดียวข้างล่างนี้คือเหตุผลเบื้องหลังกฎแทบทุกข้อของ HyperFrames เข้าใจมันแล้ว กฎทั้งหมดจะเปลี่ยนจาก "ข้อห้ามที่ต้องท่อง" เป็น "เรื่องที่เดาได้เอง"
2.1 ไม่มีการอัดหน้าจอ — มีแต่การถาม
ความเข้าใจผิดที่พบบ่อยคือคิดว่า HyperFrames "อัด" เว็บเพจแบบโปรแกรมอัดหน้าจอ: เปิดเพจ ปล่อยให้ animation เล่นไป แล้วบันทึกสิ่งที่เห็น ความจริงตรงกันข้าม — ระหว่างเรนเดอร์ ไม่มีเวลาจริงไหลผ่านเลยสักวินาที
เครื่องเรนเดอร์ทำแบบนี้แทน: ถามเพจว่า "วินาทีที่ 0.000 หน้าตาเป็นยังไง" แล้วถ่ายรูปเก็บ ถามต่อ "วินาทีที่ 0.033 ล่ะ" ถ่ายอีกรูป ไล่ถามอย่างนี้ 300 ครั้งสำหรับคลิป 10 วินาทีที่ 30 เฟรมต่อวินาที (เลข 300 นี้อยู่ใน log จริงของการเรนเดอร์บทที่ 1) เสร็จแล้วส่งรูปทั้งกองให้ FFmpeg เย็บเป็นไฟล์วิดีโอ
วิดีโอใน HyperFrames ไม่ใช่สิ่งที่ถูกอัด แต่คือคำตอบของฟังก์ชัน: ใส่เวลา t เข้าไป ได้ภาพหนึ่งเฟรมออกมา — และต้องได้ภาพเดิมทุกครั้งที่ถาม t เดิม
f(t) ทีละเฟรม (คลิป 10 วิ ที่ 30 เฟรม/วิ = ถาม 300 ครั้ง) แล้วส่งภาพทั้งกองให้ FFmpeg เย็บเป็นไฟล์ · สัญญาเดียวที่ทุกกฎของ framework ห้อยอยู่: ถาม t เดิม ได้ภาพเดิมเป๊ะ2.2 ผลข้อแรก: เวลาถูก "ประกาศ" ไม่ใช่ถูก "สั่งเล่น"
การจะตอบ "วินาที 7.2 หน้าตาเป็นยังไง" ได้ทันที เพจต้องรู้อนาคตทั้งเรื่องล่วงหน้า: อะไรโผล่เมื่อไหร่ อยู่นานแค่ไหน กำลังเคลื่อนที่ถึงจุดไหน ณ เสี้ยววินาทีนั้น ใน HyperFrames จึงไม่มีโค้ดสั่ง "เล่นเดี๋ยวนี้" มีแต่การประกาศแผนเวลาไว้ล่วงหน้า สองชั้น:
- ชั้น DOM — แต่ละ element ประกาศช่วงเวลาของตัวเองตรง ๆ บน HTML: "ฉันอยู่บนจอตั้งแต่วินาที 2.5 นาน 7.5 วินาที" ผ่าน
data-startและdata-duration(บท 3–4) - ชั้น animation — timeline ประกาศการเคลื่อนไหวทั้งเส้นจบตั้งแต่เพจโหลด แล้วหยุดนิ่งรอ ไม่เล่นเอง คนหมุนเข็มเวลาไปยังจุดที่ต้องการคือเครื่องเรนเดอร์ ไม่ใช่ตัวเพจ (บท 5)
มองผ่านเลนส์นี้ DOM ของ composition ก็คือ timeline ที่ประกาศตัวเอง — สิ่งที่โปรแกรมตัดต่อวาดเป็นแถบสีบนราง HyperFrames เขียนเป็น attribute บน element
2.3 ผลข้อสอง: อะไรที่ผันแปรตามโลกจริง คือบั๊กทั้งหมด
ถ้าเฟรมคือฟังก์ชันของเวลา แล้วมีบางอย่างในเพจตอบไม่เหมือนเดิมเมื่อถูกถาม t เดิม วิดีโอจะเสียความน่าเชื่อถือทันที — เฟรมใน preview กับเฟรมในไฟล์จริงจะไม่ใช่ภาพเดียวกัน เล่มนี้ขอปลูกเรื่องนี้เป็นปริศนาสามข้อ อ่านถึงบทที่ไขแล้วจะกระจ่างเอง:
- ทำไมห้ามใช้
Math.random()และนาฬิกาจริง ทั้งที่โค้ดเว็บทั่วไปใช้กันปกติ (บทที่ 11 ไข) - ทำไมเพจถูกห้ามเล่นไฟล์
<video>และ<audio>เอง ต้องยกสิทธิ์ให้ framework คุม (บทที่ 7 ไข) - ทำไมเครื่องตรวจปฏิเสธ animation ที่วนลูปไม่รู้จบ (บทที่ 11 ไข)
2.4 ลองพิสูจน์ด้วยตาเอง
คำถาม f(t) ไม่ใช่เรื่องเปรียบเปรย มันคือคำสั่งที่คุณยิงเองได้เดี๋ยวนี้:
$ npx hyperframes snapshot
◇ 5 snapshots saved to snapshots/
snapshots\frame-00-at-0s.png
snapshots\frame-01-at-2.5s.png
snapshots\frame-02-at-5s.png
snapshots\frame-03-at-7.5s.png
snapshots\frame-04-at-9.7s.pngนี่คือการถาม f(t) ห้าครั้งที่ t = 0, 2.5, 5, 7.5, 9.7 ได้ภาพนิ่งห้าใบโดยไม่ต้องเรนเดอร์วิดีโอทั้งไฟล์ รันซ้ำกี่รอบก็ได้ภาพชุดเดิมเป๊ะ — หลักฐานว่าไม่มีการอัด มีแต่การถาม
LEVEL 1 · พื้นฐานที่ใช้ทุกวัน
กายวิภาคของ composition#
บทนี้จะพาอ่าน composition ออกทั้งไฟล์ แล้วเริ่มสร้างการ์ดนัดประชุมทีมที่เห็นในบทที่ 1 องค์ประกอบมีน้อยกว่าที่คิด: กล่องหลักหนึ่งใบ กับลูก ๆ ที่ประกาศเวลาของตัวเอง
3.1 root — กล่องที่นิยามทั้งเรื่อง
ทุก composition มี element รากหนึ่งตัวใน body ที่ถือข้อมูลนิยามของวิดีโอทั้งหมด:
<div
id="root"
data-composition-id="main"
data-start="0"
data-duration="10"
data-width="1920"
data-height="1080"
></div>data-composition-id— ชื่อประจำตัว ใช้เป็นกุญแจตามหา timeline (บทที่ 5)data-duration— ความยาววิดีโอเป็นวินาที ตัวนี้คือคำตอบของคำถาม "เรนเดอร์กี่เฟรม" ไม่ใช่ความยาวของ animationdata-width/data-height— ขนาดเฟรมเป็นพิกเซล และ root ต้องมี CSS กำหนดขนาดจริงตามนี้ด้วย
กติกาข้อสุดท้ายสำคัญกว่าที่เห็น: ถ้า root ไม่ถูกกำหนดขนาดเป็นพิกเซลจริง ลูกที่ใช้ height: 100% จะยุบเหลือศูนย์แล้วเนื้อหาทั้งหมดไปกองที่มุมซ้ายบน — เป็นบั๊กเงียบที่เครื่องตรวจอัตโนมัติไม่ฟ้อง ต้องดู snapshot ด้วยตาเท่านั้นถึงเห็น
3.2 clip — ลูกที่ประกาศเวลาของตัวเอง
เนื้อหาทุกก้อนบนจอคือ clip: element ที่ประกาศ "ฉันอยู่บนจอช่วงไหน" ด้วย attribute สามตัว นี่คือ clip จริงจากการ์ดนัดประชุม:
<div id="headline" class="clip" data-start="0.5" data-duration="9.5" data-track-index="2">
<div class="column">
<div id="team-chip">ทีมการตลาด</div>
<div id="headline-text">ประชุมทีมรายสัปดาห์</div>
</div>
</div>data-start = โผล่วินาทีไหน · data-duration = อยู่นานเท่าไหร่ · data-track-index = อยู่รางไหน (บทที่ 4) ส่วน class="clip" เป็นธรรมเนียมให้เครื่องมือหาเจอ การซ่อน-โชว์ clip ตามเวลาเป็นหน้าที่ของ framework ล้วน ๆ — โค้ดคุณห้ามไปยุ่งกับ visibility ของมัน
3.3 กติกาที่เครื่องไม่ตรวจให้
ระหว่างเขียนเล่มนี้ ผมทดลองถอด data-start ออกจาก root แล้วรันทั้ง lint และ check — ผ่านเงียบทั้งคู่ (v0.8.8) ทั้งที่ contract ระบุว่าต้องมี บทเรียนอยู่ตรงนี้: เครื่องตรวจของ HyperFrames เก่งมากในเรื่องที่มันเห็น แต่กติกาบางข้อยังไม่มีใครตรวจแทนคุณ เขียนให้ครบตาม contract ตั้งแต่แรก อย่ารอให้เครื่องเตือน
ตัดต่อบน DOM — clips & tracks#
ในโปรแกรมตัดต่อ คุณลากคลิปวางบนราง ใน HyperFrames รางก็มีเหมือนกัน แต่มันคือตัวเลขหนึ่งตัวบน attribute และความเข้าใจผิดที่ต้องรื้อก่อนคือ: ราง (track) เป็นเรื่องของเวลา ไม่ใช่เรื่องของชั้นภาพ
4.1 ราง = เลนของเวลา
data-track-index มีหน้าที่เดียว: บอกว่า clip ไหนแชร์เลนเวลากัน กติกาคือ สอง clip บนรางเดียวกันห้ามซ้อนช่วงเวลากัน ส่วนใครบังหน้าใครบนจอเป็นเรื่องของ CSS z-index ล้วน ๆ — clip บนราง 5 ไม่ได้ "อยู่เหนือ" ราง 1
การ์ดนัดประชุมใช้ 6 ราง แยกตามบทบาท:
data-track-index คือเลนของเวลา: clip สองตัวบนรางเดียวกันห้ามซ้อนช่วงเวลากัน ส่วนใครบังใครบนจอเป็นเรื่องของ z-index ล้วน ๆ · ธรรมเนียม: เสียงแยกไปรางเลขสูง (ราง 10) ให้อ่านผังง่าย4.2 ซ้อนรางแล้วเกิดอะไร — ทดลองจริง
ผมย้ายการ์ดปิดไปราง 3 ให้ซ้อนกับแถวรายละเอียด (6.5–10 ทับ 2.5–10) แล้วรันทุกด่าน: lint เงียบ check ผ่าน และเฟรมที่ถ่ายออกมาดูปกติทุกอย่าง — ทั้งสอง clip โชว์พร้อมกันเหมือนไม่มีอะไรเกิดขึ้น
นั่นแหละคือจุดที่ต้องกลัว contract เรียกผลของการซ้อนรางว่า undefined — ไม่ได้แปลว่า "พัง" แต่แปลว่า ไม่มีสัญญาอะไรทั้งนั้น วันนี้ดูปกติ รุ่นหน้าอาจไม่ และไม่มีเครื่องตรวจตัวไหนช่วยคุณ (ยืนยันกับ v0.8.8) เจอ clip ใหม่ที่ช่วงเวลาทับของเดิม ให้หารางว่างหรือขยับเวลาอย่างตั้งใจ
4.3 ตัดต่อจริง: เลื่อน หั่น เรียงใหม่
การตัดต่อทั้งหมดคือการแก้ตัวเลขบน attribute: เลื่อนคลิป = แก้ data-start · ยืด-หด = แก้ data-duration · หั่นวิดีโอหนึ่งไฟล์เป็นหลายท่อน = สร้าง video หลาย element ชี้ไฟล์เดียวกัน แล้วใช้ data-media-start เลือกจุดเริ่มในไฟล์ต้นทางของแต่ละท่อน
ที่เก๋กว่านั้นคือ data-start รับชื่อ clip อื่นแทนตัวเลขได้: data-start="intro" แปลว่า "เริ่มเมื่อ intro จบ" และ data-start="intro - 0.5" สร้างช่วงเกยสำหรับ crossfade (ต้องอยู่คนละราง — ซ้อนรางเดียวกันผิดกติกาข้อ 4.1 เสมอ)
Timeline เดียวที่หยุดนิ่ง#
บทที่ 2 ทิ้งไว้ว่า timeline ต้อง "สร้างจบแล้วหยุดนิ่งรอ" บทนี้ลงมือจริงด้วย GSAP และไขว่าทำไมท่าเขียนบางท่าปลอดภัยกว่าอีกท่าทั้งที่ผลลัพธ์บนตาเหมือนกัน
5.1 สัญญาสามข้อ
ทุก composition ลงทะเบียน timeline เดียว แบบนี้ (โค้ดจริงจากการ์ด):
window.__timelines = window.__timelines || {};
const tl = gsap.timeline({ paused: true });
tl.fromTo(
"#headline-text",
{ autoAlpha: 0, y: 40 },
{ autoAlpha: 1, y: 0, duration: 0.8, ease: "power3.out" },
0.6
);
window.__timelines["main"] = tl;สัญญา: (ก) paused: true — คนหมุนเวลาคือเครื่องเรนเดอร์ (ข) สร้างแบบ synchronous ตอนโหลดเพจ ห้ามไปอยู่ใน setTimeout / Promise / event handler เพราะเครื่องอาจถามเฟรมก่อนโค้ดพวกนั้นวิ่ง (ค) กุญแจ "main" ต้องตรงกับ data-composition-id ของ root เป๊ะ — มันคือช่องทางเดียวที่เครื่องใช้ตามหา timeline
ตัวเลขท้ายบรรทัด (0.6) คือตำแหน่งบนแกนเวลาของเรื่อง — คุณกำลังวางแผนอนาคต ไม่ใช่สั่งเล่น
paused: true) — เข็มไม่เดินเอง เครื่องเรนเดอร์เป็นคนหมุนไปยัง t ที่ต้องการ · กุญแจ window.__timelines ต้องตรงกับ data-composition-id เป๊ะ เพราะเป็นช่องทางเดียวที่เครื่องใช้ตามหา timeline5.2 ทำไมต้อง fromTo — กลไกที่มองไม่เห็นด้วยตา
gsap.to() บอกแค่ปลายทาง แล้วให้ GSAP "จำสภาพตอนเริ่ม" เอาเอง ปัญหาคือมันจำตอนที่ถูกถามถึงครั้งแรก — และเครื่องเรนเดอร์ถามเฟรมไม่เรียงลำดับ (หลาย worker แบ่งกันถาม — บทที่ 11) สภาพตอนเริ่มที่ถูกจำจึงขึ้นกับลำดับการถาม ผิดกฎ f(t) ทันที
gsap.fromTo() ประกาศทั้งสองปลายเอง ไม่ต้องจำอะไร ถามลำดับไหนก็ได้คำตอบเดิม ผมทดลองเพิ่ม CSS transform: translateY(40px) ให้ element เดียวกับที่มี fromTo แล้วถ่ายเฟรมก่อนคิวเข้า: element หายไปเลย — fromTo ยึดสภาพเริ่มของมัน (ซ่อนไว้) ตั้งแต่เพจโหลด ค่า CSS ไม่มีผลอะไร นี่คือเหตุผลที่กติกาบอกให้ตั้งค่าเริ่มใน tween ไม่ใช่ใน CSS: ให้ความจริงมีที่อยู่ที่เดียว
5.3 ลืมลงทะเบียนแล้วใครจับ — ทดลองจริง
ผมคอมเมนต์บรรทัด window.__timelines["main"] = tl; ทิ้ง: lint เงียบสนิท แต่ check ล้มด้วยสามข้อจาก motion sidecar — ไฟล์ index.motion.json ที่ผมประกาศเจตนาไว้ข้าง composition:
✗ motion_appears_late: never reaches visible opacity but should be visible by 1.6s
Fix: The renderer seeks a paused timeline; a forward-only reveal can be skipped.
✗ motion_out_of_order: #headline-text should appear before #outro-card-line ...
✗ motion_frozen: nothing moves within #root between 2.5s and 6.5s (4s static)ไม่มี timeline = ไม่มีอะไรขยับ และเครื่องรู้เพราะผมบอกไว้ก่อนว่าอะไรควรขยับเมื่อไหร่ sidecar หน้าตาแบบนี้:
{
"assertions": [
{ "kind": "appearsBy", "selector": "#headline-text", "bySec": 1.6 },
{ "kind": "before", "a": "#headline-text", "b": "#outro-card-line" },
{ "kind": "keepsMoving", "withinSelector": "#root" }
]
}ประกาศเจตนาให้เครื่องอ่านได้ แล้วเครื่องจะเฝ้าแทนคุณ — นี่คือหัวใจของการทำวิดีโอแบบวิศวกร
สี่คำสั่งที่คือ 90% ของชีวิตจริง#
วงจรทำงานจริงวนอยู่กับสี่คำสั่ง: check → snapshot → preview → render (มี lint เป็นตัวเช็กเร็วระหว่างพิมพ์ — check รันมันซ้ำให้อยู่แล้ว ไม่ต้องเรียกเบิ้ล) เข้าใจวิธีอ่านรายงานของพวกมัน = ทำงานได้ทั้งวัน ทั้งตอนเขียนเองและตอนตรวจงานที่ AI เขียน
6.1 check — ด่านเดียวที่รันครบห้าหมวด
$ npx hyperframes check
Lint ◇ 0 errors, 0 warnings
Runtime ◇ 0 errors, 0 warnings
Layout ◇ 0 issues across 9 sample(s)
Motion ◇ 0 errors, 0 warnings
Contrast ◇ 32/32 text checks pass WCAG AA
◇ Check passedมันบูตเบราว์เซอร์หนึ่งรอบ แล้วกวาด seek ตามตาราง (ค่าเริ่มต้น 9 จุด) รันครบ: โค้ด error / เลย์เอาต์ (ตัวหนังสือล้นกรอบ ทับกัน) / เจตนา motion (บทที่ 5) / คอนทราสต์ WCAG ทุก finding มาพร้อม selector + เวลา + ข้อเสนอแก้ — อ่านจบแก้ได้เลยโดยไม่ต้องเปิดรูป
ความฉลาดที่ควรรู้: ความรุนแรงขึ้นกับความคงอยู่ ปัญหาที่เห็นแค่จุด seek เดียว (เช่นจังหวะกำลังเฟดเข้า) ถูกลดเป็น info ไม่กั้นทาง ที่ค้างหลายจุดถึงนับเป็น error และถ้าทั้งเรื่องไม่มีอะไรขยับเลย มันปฏิเสธทั้งวิดีโอ (sweep_static) — เฟรมนิ่งทำให้คำตัดสิน "ผ่าน" ทุกข้อเชื่อไม่ได้ เส้นใต้ที่ลากยาวทั้งเรื่องในการ์ดของเราเกิดมาเพื่อข้อนี้
6.2 snapshot — ตาเปล่าเห็น f(t)
npx hyperframes snapshot ถ่าย 5 เฟรมหลัก หรือ --at 2.5,7 เลือกเวลาเอง ได้ contact sheet ให้กวาดตาด้วย ใช้บ่อยที่สุดตอนไล่บั๊กเลย์เอาต์ที่เครื่องมองไม่เห็น (บทที่ 3–4 มีตัวอย่างจริงทั้งคู่)
6.3 preview — Studio สำหรับตามนุษย์
$ npx hyperframes preview --background
Studio http://localhost:3002/?view=storyboard#project/handbook-labเปิด Studio ในเบราว์เซอร์: ลาก playhead ดู timeline จริง แก้ไฟล์แล้ว reload ให้อัตโนมัติ นี่คือหน้าจอที่คุณใช้รีวิวก่อนอนุมัติเรนเดอร์ — และเป็นจุดส่งงานให้คนอื่นดูด้วย
6.4 render — จ่ายเงินเมื่อพร้อม
npx hyperframes render --quality draft ไว้วนงานเร็ว (การ์ด 10 วิ ใช้ 30–60 วินาทีบนเครื่องที่เขียนเล่มนี้) ส่งจริงค่อย --quality high เสร็จแล้วเช็กไฟล์จริงเสมอ: มีอยู่ ไม่ว่าง ความยาวถูก (ffprobe หนึ่งบรรทัดจบ)
LEVEL 2 · ระดับกลาง
สื่อจริง: วิดีโอ เสียง และฟอนต์ไทย#
ได้เวลาไขปริศนาข้อสองจากบทที่ 2: ทำไมเพจถูกห้ามเล่น <video> / <audio> เอง
7.1 framework ยึดสิทธิ์เล่นสื่อ — และหลักฐานคาตา
ถ้าเพจกด .play() เอง เวลาของสื่อจะไหลตามโลกจริง — เฟรมที่ได้จากการถาม "วินาที 4.5" จะขึ้นกับว่าถามตอนไหน ไม่ใช่ขึ้นกับ 4.5 ดังนั้น framework จึง seek สื่อให้เองทุกเฟรม: เวลาในไฟล์สื่อ = เวลาของเรื่อง ลบ data-start ของ clip
ผมพิสูจน์เรื่องนี้ด้วยวิดีโอทดสอบที่มี timestamp ฝังในภาพ: วางไว้ที่ data-start="3" แล้วถ่ายเฟรมของเรื่องที่วินาที 4.5 — ในภาพเห็น timestamp ของไฟล์ต้นทางชี้ 00:00:01.500 พอดีเป๊ะ (4.5 − 3.0) ไม่มีการเล่น มีแต่การเปิดไปหน้าที่ถูกถาม
7.2 กติกาการวางสื่อ
<video id="broll" class="clip" src="assets/broll.mp4"
data-start="3" data-duration="3.5" data-track-index="5" muted playsinline></video>
<audio id="chime" src="assets/chime.m4a"
data-start="0.5" data-track-index="10" data-volume="0.8"></audio><video>ต้องmuted+playsinlineเสมอ — เสียงเป็นหน้าที่ของ<audio>แยกต่างหาก แม้ใช้ไฟล์เดียวกัน<audio>ไม่ใส่data-duration= ใช้ความยาวจริงของไฟล์data-volumeคือระดับเสียงพื้นฐาน อยากเฟดให้ tweenvolumeบน timeline- ห้ามเรียก
.play()/.pause()/.currentTimeเองเด็ดขาด — คุณเพิ่งอ่านเหตุผลไปใน 7.1
เรนเดอร์แล้วเสียงถูกรวม (mux) ลงไฟล์จริง (log ขึ้น hasAudio: true และ ffprobe เห็น stream aac) — ทั้งหมดนี้ทำงานที่ความลึกไหนของ DOM ก็ได้ รวมถึงใน sub-composition (บทที่ 8)
7.3 ฟอนต์ไทย — ด่านที่คนไทยเจอก่อนใคร
พิมพ์ชื่อฟอนต์เฉย ๆ (font-family: "Sarabun") โดยไม่มี @font-face ชี้ไฟล์ = lint แดงทันที:
✗ font_family_without_font_face: Font families used without @font-face declaration:
sarabun, noto serif thai. ... Text will fall back to a generic font,
producing incorrect typography in the video.ทางแก้คือดาวน์โหลด .woff2 มาไว้ในโปรเจกต์แล้วประกาศ @font-face ชี้ไฟล์ local และมีของแปลกหนึ่งจุดที่ควรรู้ (v0.8.8): ประกาศไว้ในไฟล์ CSS แยกแล้ว link เข้ามา — lint เข้าใจ แต่ตัวตรวจตอนรัน (StaticGuard) มองไม่เห็นและเตือนหลอกทั้งที่ผลจริงถูกต้อง (ผมยันจากเฟรมใน MP4 แล้ว) ย้าย @font-face มา inline ใน style ของเพจแล้วเสียงเตือนหาย — แนะนำ inline ให้จบเรื่อง
สุดท้าย กติกาไทยแท้ ๆ หนึ่งข้อ: line-height ของข้อความไทยบนวิดีโอต้อง ≥ 1.3 ไม่งั้นสระบน-วรรณยุกต์ชนบรรทัดบนจนเครื่องตรวจเลย์เอาต์ฟ้อง
Sub-composition — ประกอบเรื่องจากชิ้นส่วน#
การ์ดใบเดียวอยู่ไฟล์เดียวได้สบาย แต่พองานโต — หลายฉาก หรืออยากใช้การ์ดปิดเดิมซ้ำในคลิปอื่นของทีม — คุณต้องแตกไฟล์ และการแตกไฟล์ใน HyperFrames มีกลไกขนส่งเฉพาะตัวที่ต้องเข้าใจก่อน ไม่งั้นเจอบั๊กที่ "ทุกด่านผ่านแต่ render พัง"
8.1 template คือตู้คอนเทนเนอร์ — ของนอกตู้ถูกทิ้งท่าเรือ
ตอน mount ไฟล์ลูก runtime ทำแค่นี้: fetch ไฟล์ → หา template → โคลนเฉพาะของข้างใน ไปวางในเรื่อง ทุกอย่างนอก template — รวมทั้ง head ทั้งหัว — ถูกทิ้ง
ผลคือกฎเหล็กข้อแรก: style และ script ของไฟล์ลูกต้องอยู่ใน template วางไว้ใน head ตามสัญชาตญาณ HTML ปกติ = สไตล์ทั้งชุดหายเงียบ อาการคือตัวหนังสือจิ๋วไม่ติดสไตล์กองมุมซ้ายบน — ด่าน static ทุกตัวผ่านหมดเพราะแต่ละไฟล์ถูกต้องในตัวเอง พังเฉพาะตอนประกอบ
template — ทุกอย่างนอกนั้นรวมทั้ง head ถูกทิ้งตั้งแต่ท่าเรือ (สไตล์ที่เผลอวางใน head จึงหายเงียบทั้งชุด) · และชื่อ composition ต้องตรงกันสามจุด: ช่องเสียบ = root ของไฟล์ลูก = กุญแจ timeline8.2 ของจริง: การ์ดปิดของเรา
ผมแตกการ์ดปิดออกเป็น compositions/outro.html โครงตามนี้ (ตัดให้สั้น):
<body>
<template>
<style>
#root { position: absolute; inset: 0; }
#outro-card-line { position: absolute; left: 200px; top: 920px; ... }
</style>
<div id="root" data-composition-id="outro-card" data-width="1920" data-height="1080">
<div id="outro-card-line">เตรียมอัปเดตงานของตัวเอง คนละ 1 นาที แล้วเจอกันครับ</div>
</div>
<script>
const outroTl = gsap.timeline({ paused: true });
outroTl.fromTo("#outro-card-line", { autoAlpha: 0, y: 16 },
{ autoAlpha: 1, y: 0, duration: 0.8 }, 0.1);
window.__timelines["outro-card"] = outroTl;
</script>
</template>
</body>ฝั่งเรื่องหลัก clip ตัวเดิมกลายเป็น "ช่องเสียบ":
<div id="outro" data-composition-id="outro-card"
data-composition-src="compositions/outro.html"
data-start="6.5" data-duration="3.5" data-track-index="4"
data-width="1920" data-height="1080"></div>สังเกตสามอย่าง: (ก) id ต้องตรงกันสามจุด — บนช่องเสียบ บน root ของไฟล์ลูก และกุญแจ __timelines — เพราะชื่อนี้คือทางเดียวที่เครื่องใช้จับคู่ timeline อย่าตั้งชื่อช่องเสียบเป็น outro-mount เด็ดขาด (ข) เวลาใน timeline ลูกเป็นเวลาท้องถิ่น: 0.1 ของมันคือ 6.6 ของเรื่อง (ค) สไตล์ root ของไฟล์ลูกต้องเขียนผ่าน #root — ตอนประกอบ CSS ทุกกฎถูก scope ด้วย composition-id เป็น descendant selector คลาสที่แปะบน root เองจึงไม่มีวัน match แล้วกฎทั้งชุดหายเงียบ
8.3 ด่านตรวจของบทนี้: snapshot กลางฉาก
บั๊ก mount ทั้งตระกูลนี้ผ่าน static check ได้หมด ด่านเดียวที่จับคือถ่ายเฟรมกลางช่วงของ slot แล้วดูด้วยตา — ผมรัน snapshot --at 7.5 แล้วเห็นการ์ดปิดขึ้นครบถูกตำแหน่ง ฟอนต์มาเต็ม นั่นคือเสียงเขียวจริงของบทนี้ (check ก็ยังผ่านเต็มทุกหมวด รวม motion sidecar ที่ assert ทะลุเข้าไปหา element ในไฟล์ลูกได้ด้วย)
Motion ที่ดูเป็นงานอาชีพ#
ของที่แยกงานมือใหม่กับงานโปรไม่ใช่จำนวนเอฟเฟกต์ แต่คือวินัยไม่กี่ข้อที่ใช้ซ้ำทั้งเรื่อง บทนี้สรุปวินัยที่การ์ดของเราใช้จริง แล้วปิดด้วยเครื่องมือที่ช่วยให้ไม่ต้องประดิษฐ์ท่าเอง
9.1 วินัยที่การ์ดใบนี้ใช้
- เข้านุ่ม ตั้งใจ: ทุกทางเข้าใช้ ease ตระกูล out (
power2.out,power3.out) — พุ่งมาแล้วเบรกนิ่ม ตาคนอ่านว่า "ตั้งใจมา" ไม่ใช่ "หล่นมา" ระยะทางสั้น (16–40px) และ 0.5–0.8 วินาทีก็พอ - ทีละจังหวะ: ของหลายชิ้นเข้าพร้อมกัน = ตาไม่รู้จะดูอะไร แถวรายละเอียดสามแถวใช้
stagger: 0.25— โครงเดียวกัน เหลื่อมเวลากัน สายตาถูกจูงทีละบรรทัด - อย่าปล่อยเฟรมตาย: จอนิ่งสนิทหลายวินาทีดูเหมือนวิดีโอค้าง (และ
checkปฏิเสธจริงตามsweep_static— บทที่ 6) การ์ดเราแก้ด้วยเส้นใต้เส้นเดียวที่ลากช้า ๆ ทั้งเรื่อง:ease: "none"ยาว 8.8 วินาที เคลื่อนไหวพอให้มีชีวิต เบาพอไม่แย่งซีน - จังหวะไทยสองข้อ (จ่ายราคามาแล้วจากโปรเจกต์จริง): หั่นประโยคไทยใส่
spanหลายตัวเพื่อ animate ทีละคำ — ถ้า markup มีช่องว่างระหว่าง span จะเกิดวรรคปลอมกลางคำบนจอ ("สิบปี ที่...") ที่เครื่องตรวจไม่มีวันเห็น ต้องเขียน span ชิดกันและดู contact sheet เอง · ข้อความเข้าบนพื้นสี ใช้ทางเข้าแบบ binary (tl.setโผล่ทั้งก้อน) แทนการเฟด — ตัวตรวจ contrast ชอบจับเฟรมกลางเฟดที่สีจางพอดี แก้ทีเดียวได้ทั้งความถูกและจังหวะที่เฉียบกว่า
9.2 หาท่าจากคลังก่อนประดิษฐ์เอง
HyperFrames มีคลังท่าสำเร็จ ค้นด้วยภาษาอังกฤษ (คลังทำ index เป็นอังกฤษ — ถามไทยได้ผลว่างเปล่า) และถามด้วยผลลัพธ์ที่อยากได้ ไม่ใช่ชื่อกลไก:
$ npx hyperframes catalog --query "gentle continuous motion to keep a static card alive"
echo-trail component A moving element renders with N ghosted copies...
telemetry-hud component Quiet mono debug-HUD readouts frame a slotted subject...
chart-story component One chart builds from data in reading order...
...
162 items. Run "hyperframes add <name>" to install.เจอที่ใช่ก็ติดตั้งเข้าโปรเจกต์: npx hyperframes add whip-pan-cut ได้ไฟล์มาที่ compositions/components/ พร้อมก๊อปลงเรื่อง ส่วนรอบที่ผมค้นจริงข้างบน — ผลลัพธ์ล้วนเป็นท่าโชว์ ขณะที่โจทย์ผมคือเส้นใต้เส้นเดียว หนึ่งบรรทัด tween เขียนเองถูกกว่า: ค้นก่อนเสมอ แต่กล้าตอบว่า "ไม่มีอะไรเรียบง่ายเท่าเขียนเอง"
LEVEL 3 · ระดับสูง
จากโจทย์ถึงสตอรีบอร์ด — ทำงานกับ AI#
งานวิดีโอจริงไม่ได้เริ่มที่โค้ด มันเริ่มที่เจตนา และเมื่อคนลงมือคือ AI ชั้นเจตนายิ่งต้องเขียนเป็นลายลักษณ์ — เพราะทุกอย่างที่ไม่ได้เขียน agent จะเดาแทน
10.1 BRIEF.md — สัญญาเจตนา
BRIEF คือไฟล์เดียวที่ตอบว่า ทำไปทำไม ให้ใครดู ยาวเท่าไหร่ ห้ามอะไร ตัวอย่างจริงจากคลิปที่ผม ship แล้ว (คลิปแนะนำตัวของ tayakorn.dev):
message: "สิบปีที่ไม่มีชั้นไหนหายไป — มีแต่ทับถมขึ้น"
destination: vertical-social
aspect: 1080x1920
length: 40s
audience: "ผู้อ่านสายเรียนลึกชาวไทย + คนที่กำลังประเมินตัวตนของทยากร"และส่วนที่มีค่าที่สุดคือ Notes — ข้อห้ามที่ตัดสินใจแล้ว: "ห้ามสีเย็นทุกเฉด · ห้าม bounce/elastic · ตัวเลขทุกตัวต้องตรวจได้จริง ณ วันเรนเดอร์" กติกาพวกนี้เขียนครั้งเดียว คุมงานทุกเฟรมที่ agent เขียนต่อจากนั้น
10.2 STORYBOARD.md — แผนรายเฟรมที่เครื่องอ่านออก
ระดับถัดลงมาคือสตอรีบอร์ด: ไฟล์ markdown หนึ่งไฟล์ frontmatter คุมภาพรวม (format / message / arc / audience) แล้วหนึ่งเฟรมต่อหนึ่งหัว ## Frame N — ชื่อ พร้อม metadata เป็น bullet: status (outline → built → animated) · duration · src ชี้ไฟล์ฉากเมื่อสร้างแล้ว
มันไม่ใช่แค่เอกสารคน — เครื่อง parse มันจริง: ผมเขียนแผนสามเฟรมของการ์ดเวอร์ชันถัดไป แล้วถาม Studio ผ่าน API ได้ manifest กลับมาครบ (HTTP 200): สามเฟรม เฟรมสุดท้าย status: built, src: compositions/outro.html, srcExists: true — สถานะแผนกับของจริงบนดิสก์เชื่อมถึงกัน และ Studio เปิดหน้า contact sheet ของแผนนี้ให้ดูอัตโนมัติ (?view=storyboard)
10.3 บทบาทของคุณเมื่อ agent เป็นคนพิมพ์
วงจรที่ใช้ได้จริง: คุณเขียน BRIEF (เจตนา + ข้อห้าม) → agent ร่างสตอรีบอร์ด → คุณรีวิวที่ชั้นแผน (แก้ตรงนี้ถูกกว่าแก้ตอนโค้ดเสร็จหลายเท่า) → agent สร้างทีละฉาก → คุณตรวจรับด้วยเครื่องมือบทที่ 6: อ่านรายงาน check กวาด contact sheet และรีวิวใน Studio ก่อนอนุมัติ render — ทักษะทั้งเล่มนี้แหละคือทักษะตรวจรับ
กลไกภายใน: เครื่องถ่ายทีละเฟรม#
บทนี้เปิดฝากล่องเครื่องเรนเดอร์ แล้วปริศนาทุกข้อที่เล่มนี้ปลูกไว้จะถูกไขพร้อมกันด้วยเหตุผลเดียว
11.1 ไปป์ไลน์จริงจาก log
เรนเดอร์การ์ด 10 วินาทีหนึ่งรอบ log เล่าไปป์ไลน์ครบ (ตัดมาเฉพาะสาระ):
phase: capture totalFrames: 300 workerCount: 4 captureMode: beginframe
phase: encode (FFmpeg เย็บเฟรมเป็นวิดีโอ)
phase: assemble hasAudio: true (ผสมเสียง ปิดไฟล์)จุดที่ควรหยุดอ่านคือ workerCount: 4 — เครื่องเปิดเบราว์เซอร์หลายตัวแบ่งกันถามคนละช่วงเฟรมพร้อมกัน เฟรม 200 อาจถูกถ่ายก่อนเฟรม 50 ไม่มีการ "เล่นจากต้นจนจบ" อยู่ในระบบเลยแม้แต่ขั้นตอนเดียว
workerCount: 4 — สี่เบราว์เซอร์แบ่งกันถามคนละช่วงเฟรมพร้อมกัน เฟรม 200 จึงอาจถูกถ่ายก่อนเฟรม 50 · จากนั้นสามเฟสตามลำดับ capture → encode → assemble (ผสมเสียง) · ไม่มี “เล่นจากต้นจนจบ” อยู่ในระบบแม้แต่ขั้นตอนเดียว — รากของกฎ determinism ทุกข้อ11.2 ปริศนาทั้งเล่ม ไขด้วยประโยคเดียว
ทุกกฎของ HyperFrames คือผลของข้อเท็จจริงที่ว่า เฟรมถูกถามไม่เรียงลำดับและถามซ้ำได้:
- ห้าม
Math.random()/ นาฬิกาจริง / fetch ตอนเรนเดอร์ — ค่าพวกนี้ตอบไม่เหมือนเดิมต่อคำถามเดิม (ปลูกไว้บทที่ 2) - ห้ามเพจเล่นสื่อเอง — เวลาไหลจริงผูกคำตอบกับ "ตอนไหนที่ถาม" (ไขแล้วบทที่ 7)
- ห้าม animation วนไม่รู้จบ (
repeat: -1) — ตำแหน่งในลูปนิยามจากเวลาไม่ได้ถ้าไม่รู้จำนวนรอบ ใช้จำนวนจำกัดที่คำนวณจากความยาวจริง (ปลูกไว้บทที่ 2) - timeline ต้อง paused และ fromTo ชนะ to — การ "จำสภาพตอนเริ่ม" ขึ้นกับลำดับการถาม (ไขแล้วบทที่ 5)
- แม้แต่ hover / scroll ก็ต้องห้าม — เครื่องเรนเดอร์ไม่มีมือ ไม่มี input event ใด ๆ
11.3 พิสูจน์เชิงประจักษ์ — hash ไม่โกหก
ผมเติม Math.random() หนึ่งบรรทัดให้เส้นใต้เอียงแบบสุ่ม แล้วถ่ายเฟรมที่ t=5 สองรอบ จากนั้นถอดออกแล้วถ่ายซ้ำอีกสองรอบ เทียบ hash ระดับพิกเซล:
มี Math.random run1: 98820fd7... run2: 5490ad2c... ← คนละภาพ
โค้ดสะอาด run1: cdaa3183... run2: cdaa3183... ← เหมือนกันทุก byteโค้ดสะอาดตอบ f(t) เดิมเป๊ะทุกครั้งจริง ๆ ไม่ใช่วาทกรรม — และหนึ่งบรรทัดสุ่มทำลายมันทันที ลองเองได้ในหนึ่งนาที: snapshot --at 5 สองรอบแล้วเทียบไฟล์
กฎทุกข้อที่ดู "จุกจิก" ของ framework นี้ คือราคาของสัญญาเดียว: ถามเฟรมไหน เมื่อไหร่ กี่รอบ ก็ได้ภาพเดิม
เรนเดอร์สเกลจริง & ส่งมอบ#
บทที่ 1 สัญญาไว้ว่า "คลิปโครงเดียวกันร้อยชื่อร้อยเวอร์ชัน" ได้เวลาจ่ายคืน — และนี่คือจุดที่วิดีโอแบบโค้ดทิ้งห่างโปรแกรมตัดต่อขาดลอย
12.1 เปิดช่องรับตัวแปร
ประกาศตัวแปรบน html (ชนิด + ค่า default) แล้วผูกเข้ากับ element ด้วย data-var-text:
<html data-composition-variables='[
{"id":"team","type":"string","label":"ชื่อทีม","default":"ทีมการตลาด"},
{"id":"room","type":"string","label":"สถานที่","default":"ห้องประชุม 2 ชั้น 5"}
]'>
...
<div id="team-chip" data-var-text="team">ทีมการตลาด</div>ข้อความในไฟล์กลายเป็นค่า fallback ตัวแปรทุกตัวยังโผล่เป็น CSS custom property (var(--team)) ให้ฝั่งสไตล์ใช้ด้วย ระวังจุดสับสนเดียวของเรื่องนี้: ตอนประกาศเป็น array ของ schema แต่ตอนส่งค่าเป็น object ธรรมดา ({"team": "ทีมบัญชี"})
12.2 rows.json → ฝูงวิดีโอ
{
"rows": [
{ "name": "marketing", "team": "ทีมการตลาด", "room": "ห้องประชุม 2 ชั้น 5" },
{ "name": "accounting", "team": "ทีมบัญชี", "room": "ห้องประชุม 1 ชั้น 3" },
{ "name": "it", "team": "ทีมไอที", "room": "ออนไลน์ (Teams)" }
]
}$ npx hyperframes render --batch rows.json --output "renders/{name}.mp4" --quality draft
◇ renders\it.mp4 744.4 KB · 10.0s video · rendered in 30.0s
Batch complete: 3 completed.
Manifest: renders\manifest.jsonสามทีม สามไฟล์ หนึ่งคำสั่ง — ผมเปิดเฟรมจาก accounting.mp4 ดู: ชิปขึ้น "ทีมบัญชี" ห้องขึ้น "ห้องประชุม 1 ชั้น 3" ครบถ้วน ในโปรแกรมตัดต่อ งานนี้คือเปิดโปรเจกต์แก้มือสามรอบ ที่นี่คือเพิ่มบรรทัดใน JSON แถมได้ manifest.json สรุปผลทุกไฟล์ไว้ตรวจย้อน
12.3 นิสัยส่งมอบ
วนงานด้วย --quality draft (เร็วกว่า high หลายเท่า) ส่งจริงค่อย --quality high แล้วปิดจ๊อบด้วยการยันไฟล์เสมอ — สามคำถาม: ไฟล์มีจริง ขนาดไม่ใช่ศูนย์ ความยาวตรงตั้งใจ:
$ ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1 renders/accounting.mp4
duration=10.000000ส่งท้าย — สิ่งที่จะอยู่นานกว่าเวอร์ชัน#
เล่มนี้ทดสอบกับ hyperframes v0.8.8 ที่ยังเดินเร็ว — ชื่อคำสั่งอาจขยับ ด่านตรวจจะหนาขึ้น (กติกาบางข้อที่วันนี้เครื่องยังไม่จับ พรุ่งนี้อาจจับ) แต่สังเกตไหมว่าเนื้อแท้ของทุกบทกลับไม่ได้ผูกกับเวอร์ชันเลย: เฟรมคือฟังก์ชันของเวลา · เวลาถูกประกาศ ไม่ใช่ถูกสั่ง · เครื่องตรวจเห็นไม่ครบ ตาและ contract คือด่านสุดท้าย · เจตนาที่เขียนเป็นลายลักษณ์คุมงานได้ไกลกว่าคำสั่งปากเปล่า
หลักพวกนี้ไม่ใช่ของ HyperFrames ด้วยซ้ำ — มันคือหลักของการทำวิดีโอแบบวิศวกร และตอนนี้มันอยู่ในมือคุณครบทั้งวง: เขียน composition จากศูนย์ อ่านรายงานเครื่องออก ไขบั๊กจากกลไกไม่ใช่จากการเดา และเรนเดอร์งานจริงส่งมอบได้ทั้งใบเดียวและทั้งฝูง
อ่านต่อ: Harness Engineering#
บทที่ 10 เปิดประตูไว้แล้ว: งานวิดีโอสมัยนี้คือการกำกับ agent — เขียนเจตนา วางข้อห้าม ตรวจรับผลงาน ถ้าอยากเข้าใจกลไกฝั่งนั้นให้ลึกเท่าที่เล่มนี้พาลงลึกฝั่งวิดีโอ ต่อด้วย Harness Engineering Handbook: Agent = Model + Harness ผ่ากลไก agent loop, tools, memory จนถึงผ่า Claude Code ทั้งตัว
If this was useful —buy me a coffee