aimDISCORD · Nhật ký phát triển
Từ bản 0.1.0 tới 0.12.0 và bản sửa "ngắm bằng con trỏ", ngày 14–16/10/2026. 48 commit, 52 bài test tự động, chạy trên server Ubuntu 10.10.10.21 và Vercel.
---
1. aimDISCORD là gì
aimDISCORD là một Discord Activity: trò chơi luyện aim kiểu Valorant chạy ngay trong cửa sổ Discord. Người chơi bấm vào biểu tượng Activity trong kênh thoại hoặc kênh chat là chơi được, không cần cài gì.
Bot đi kèm có:
- bảng xếp hạng theo phòng, server và toàn cầu;
- rank từ Sắt 1 tới Tỏa Sáng, tính theo từng chế độ;
- thử thách hằng ngày, thách đấu 1v1;
- ảnh thẻ hồ sơ, vai trò theo rank;
- thông báo kỷ lục trong chat.
Hiện có:
- 10 chế độ: Gridshot, Spidershot, Microshot, Headshot, Timing, Giữ góc (Peek), Tracking, Strafe, Switching, Phản xạ.
- Phòng bắn 3D góc nhìn thứ nhất, chuột xoay đúng độ nhạy Valorant.
- Skin súng: khoảng 1.400 skin Valorant để chọn, 21 skin "cầm tay" và ảnh súng tự tải lên.
- Tab Đố vui với 5 trò cực khó về map, agent và súng.
- Thẻ "Valorant của bạn": rank thật lấy từ Riot ID.
Cấu trúc:
activity/: phần chạy trong Discord (Vite + TypeScript + three.js), host trên Vercel.src/: server Node.js, gồm bot discord.js, API web, SQLite và các ảnh thẻ vẽ bằng@napi-rs/canvas. Chạy trong Docker trên server riêng, ra internet qua Tailscale Funnel.src/domain/game.js: luật của mọi chế độ, dùng chung cho cả trình duyệt lẫn server.
---
2. Ý tưởng: tại sao làm khi đã có Aim Lab, Kovaak's, 3D Aim Trainer?
Các ứng dụng luyện aim đã có sẵn và làm rất tốt:
- Aim Lab (miễn phí, trên Steam);
- Kovaak's (trả phí);
- 3D Aim Trainer (app.3daimtrainer.com, chạy trên web và là nơi mình tham khảo nhiều nhất).
Làm lại không phải để thắng họ về độ sâu, mà vì có những chỗ trống họ không lấp:
- Ở ngay trong Discord. Nhóm bạn chơi Valorant vốn đã ngồi sẵn trong kênh thoại. Không phải cài Steam, không phải mở trình duyệt riêng: bấm một nút là cả nhóm cùng vào, thấy điểm của nhau trong cùng một phòng.
- Xếp hạng theo server. Aim Lab so mình với cả thế giới; ở đây so với chính bạn bè trong server, có vai trò rank, thông báo kỷ lục và thách đấu 1v1 ngay trong chat. Cạnh tranh giữa người quen mới là thứ giữ người ta quay lại.
- Chỉ dành cho Valorant. · Độ nhạy dùng đúng thang của Valorant (0,07° mỗi lần chuột nhích × độ nhạy, góc nhìn ngang 103°), nên gõ đúng con số trong game là chuột xoay y hệt. · Rank dùng biểu tượng thật của game, tâm ngắm đọc được mã profile của Valorant. · Có skin súng Valorant, chế độ Giữ góc mô phỏng kiểu đối thủ ló ra từ góc, và câu đố về callout.
- Tiếng Việt từ đầu tới cuối, kể cả ảnh thẻ trong chat.
- Để học. Một dự án đủ lớn để chạm vào gần như mọi tầng của phần mềm: · đồ họa 3D trong trình duyệt, xử lý chuột ở mức thấp; · chống gian lận phía server, cơ sở dữ liệu; · bot Discord, OAuth, Docker, mạng, triển khai; · xử lý ảnh, thiết kế giao diện.
---
3. Dòng thời gian
Giai đoạn 1: nền móng (0.1 → 0.5, ngày 14–15/10)
- 0.1.0, trò chơi cơ bản: Gridshot, Microshot, Tracking, Phản xạ. · Mỗi lượt có một seed (số khởi tạo ngẫu nhiên). Trình duyệt ghi lại mọi cú bấm và chuyển động chuột thành log, gửi lên server. Server chơi lại đúng lượt đó từ seed và log để tự tính điểm, không tin con số trình duyệt gửi lên. · Có bảng xếp hạng, lệnh
/bxh,/hoso, Docker, sao lưu cơ sở dữ liệu hằng ngày. - 0.2.0: rank Sắt 1 → Bất Tử 3 với RR, và Tỏa Sáng cho top 10 toàn cầu. Trình sửa tâm ngắm đọc và ghi mã của Valorant.
- 0.3.0: biểu tượng rank thật của Valorant (theo chính sách nội dung fan của Riot). Thêm màn mở đầu, hiệu ứng thăng hạng làm vỡ huy hiệu cũ.
- 0.4.0: sảnh là một trường bắn sống có mục tiêu bắn được. Thêm dịch vụ Tailscale Funnel để đưa API ra internet.
- 0.5.0: Spidershot, Headshot, Strafe; thử thách hằng ngày, thách đấu 1v1, ảnh thẻ, vai trò theo rank.
Giai đoạn 2: tìm giao diện (0.6 → 0.7)
- 0.6.0: giao diện "Radianite Night", nền tối, kính mờ.
- 0.7.0: giao diện "Cover Issue" kiểu tạp chí thời trang, hồng phấn và bạc, chữ Playfair Display. Lần đầu có độ nhạy chuột với khóa con trỏ.
Giai đoạn 3: giống Valorant thật (0.8 → 0.10, tối 15/10 tới sáng 16/10)
- Sửa độ nhạy: báo lỗi "độ nhạy không hoạt động", sau đó "vẫn chậm hơn thực tế". Đổi sang đúng thang Valorant, rồi đổi tiếp cách tâm ngắm di chuyển theo phối cảnh (xem mục 4.1).
- 0.8.0, chuyển sang 3D: · mỗi lượt là một căn phòng 3D, chuột xoay camera như game FPS thật; · thêm Timing (mục tiêu phồng lên rồi tan) và Switching (ba mục tiêu trôi, giữ tâm đủ lâu thì vỡ); · bắt đầu mùa 2: rank và bảng xếp hạng làm lại từ đầu vì cảm giác chơi 3D khác hẳn bản 2D.
- Lần triển khai đầu lên Discord. Gặp lỗi Discord "User-installed Apps require verification to use activities in servers with more than 25 members", giải quyết bằng cách cài app vào server thay vì cài cho người dùng.
- 0.9.0, skin súng: · súng 3D tự dựng với 7 lớp hoàn thiện; · bộ chọn khoảng 1.400 skin Valorant thật lấy từ valorant-api.com; · ảnh súng góc nhìn thứ nhất khớp với ảnh chụp màn hình
fov.pngtừ game, kích thước 54% chiều cao màn hình; · người chơi tự tải ảnh súng của mình lên. - Cắt skin từ video: thử cắt ảnh súng cầm tay từ video giới thiệu skin (Champions 2021, rồi 20 khẩu Vandal). Cuối cùng bỏ, dùng bộ 21 ảnh skin trong thư mục
skins/thay thế. - 0.10.0: giao diện "Tactical HUD" giống client game (chữ Chakra Petch, màu xanh ngọc #2ee6c5, góc vát).
- Thử đầu robot và đầu agent làm mục tiêu, sau đó quay lại quả cầu.
- Ba vòng tối ưu FPS, kết thúc bằng một đợt kiểm tra kỹ toàn bộ (xem mục 4.6).
Giai đoạn 4: giống Valorant hơn nữa (0.11, ngày 16/10)
- Chế độ Giữ góc: có thùng, trụ cửa và mép tường; đầu địch trượt ra từ một góc, đứng 0,45–0,9 giây rồi rụt lại; bắn vào vật cản là trượt; bắn càng sớm càng nhiều điểm.
- Bối cảnh map trên tường sau. Sau đó bị bỏ vì chỉ là ảnh toàn cảnh, không phải cảnh thật trong map.
- Tab Đố vui bản đầu, 4 trò.
Giai đoạn 5: gọn và khó (0.12 cùng bản sửa, ngày 16/10)
- Trang chủ: một nút CHƠI NGAY và 3 thẻ (Giữ góc, Thử thách hôm nay, Đố vui).
- Đố vui cực khó: 357 ảnh chụp trong map từ Valorant wiki, đồng hồ cho từng câu.
- "Valorant của bạn": rank thật qua HenrikDev API.
- Thêm: hỏi độ nhạy ở lần đầu vào app, Khởi động 5 phút, âm thanh khi trúng, khoe kết quả lên kênh.
- Bản sửa ngắm bằng con trỏ: khi Discord không cho khóa chuột (cửa sổ nhỏ, không toàn màn hình).
---
4. Khó khăn và cách giải quyết
4.1. Độ nhạy chuột không giống trong game
- Vấn đề: độ nhạy "không hoạt động", sau đó "chậm hơn thực tế".
- Nguyên nhân: · Ban đầu tâm ngắm chạy trên mặt phẳng 2D theo pixel, không theo góc quay. · Khi chưa khóa được con trỏ, trình duyệt báo chuyển động theo pixel CSS, không theo "count" của chuột. Màn hình có tỉ lệ 125% hay 150% thì số liệu bị lệch.
- Cách giải quyết: · Dùng đúng công thức của Valorant: mỗi count xoay 0,07° × độ nhạy. · Nhân chuyển động với
devicePixelRatiokhi không khóa được chuột. · Đổi sang camera 3D thật, quay yaw và pitch, để góc nhìn 103° khớp với game. - Bài học: muốn "giống game" thì phải hiểu game đo đạc thế nào (đơn vị gì, góc nhìn bao nhiêu), không đoán bằng mắt.
4.2. Ai cũng có thể gửi điểm giả
- Vấn đề: trình duyệt nằm trong tay người chơi, họ sửa được mọi thứ.
- Cách giải quyết: · Luật chơi viết trong
src/domain/game.js, dùng chung cho trình duyệt và server. · Server nhận seed và log sự kiện[loại, thời gian, x, y], tự chơi lại để tính điểm. · Thêm các cờ nghi vấn: hai phát trúng cách nhau dưới 70 ms, trúng 100% mà quá nhanh, không có chuyển động chuột nào, thời gian chơi ngắn hơn thời lượng lượt. · Lượt bị gắn cờ được giữ lại nhưng không lên bảng xếp hạng. - Bài học: · Kiến trúc "server có thẩm quyền" (authoritative server). · Hàm sinh số ngẫu nhiên có seed (mulberry32), cho cùng kết quả trên mọi máy. · Luật chơi phải tất định (deterministic): cùng đầu vào thì ra cùng kết quả.
4.3. Discord chặn Activity ở server trên 25 người
- Vấn đề: app cài theo kiểu "user install" thì Discord yêu cầu xác minh khi chạy trong server đông người.
- Cách giải quyết: cài app vào server (guild install) bằng link mời có scope
applications.commandsvàbot.
4.4. Một repo "skin swapper" trên GitHub
- Vấn đề: có người gợi ý repo
valorant-skin-swapper-2026. - Cách giải quyết: · Không clone, chỉ đọc mô tả và file. · Nhận ra dấu hiệu phát tán mã độc quen thuộc: hứa "đổi skin miễn phí", bắt tắt antivirus, tải file nén có mật khẩu. · Viết nội dung báo cáo gửi GitHub.
- Bài học: nhận diện chiêu lừa nhắm vào game thủ. Không bao giờ chạy code từ nguồn lạ chỉ vì hứa cho đồ miễn phí trong game.
4.5. Cắt ảnh súng ra khỏi video
- Mục tiêu: có ảnh súng cầm tay góc nhìn thứ nhất cho từng skin, như
fov.png. - Cách đã thử, theo thứ tự: 1. Công cụ xóa nền online: lỗi và tốn 1 credit. 2. So sánh khung hình (frame differencing): lấy phần thay đổi giữa các khung hình. 3. GrabCut (OpenCV): lúc đầu chọn nhầm khung hình đang ngắm (ADS). Sửa bằng cách chọn khung khớp mẫu (template fit). 4. Bản đồ điểm ảnh tĩnh: nền vẫn còn lem, nên làm bản đồ những điểm ảnh đứng yên trên ≥ 85% số khung, xóa nền nhưng bảo vệ phần lõi súng, rồi xóa tay ở các vùng còn sót. 5. Viền răng cưa: làm mịn bằng khử răng cưa (anti-aliasing) ở biên.
- Kết quả: người dùng thấy chưa đạt và thay bằng bộ ảnh tự chuẩn bị.
- Bài học: · Xử lý ảnh thực tế khó hơn lý thuyết rất nhiều. · Biết khi nào nên dừng một hướng đi tốn công và đổi nguồn dữ liệu.
4.6. Game giật, "sao Aim Lab mượt mà mình không?"
Ba vòng tối ưu, vòng cuối kiểm tra kỹ từng thứ:
- Một canvas WebGL duy nhất: trước đó súng là một lớp ảnh HTML riêng và tâm ngắm là một canvas toàn màn hình. Gộp súng vào cùng canvas 3D (vẽ trong một "cảnh HUD" riêng), tâm ngắm chỉ còn canvas 96 px ở giữa.
- Phòng không cần ánh sáng: tường dùng
MeshBasicMaterial, mọi đường lưới gộp thành mộtLineSegmentscó sẵn màu, nên một lần vẽ là xong cả phòng. - Bỏ: khử răng cưa (MSAA), sương mù, tone mapping. Shader dùng độ chính xác
mediump. - Mục tiêu dùng chung vật liệu. Mảnh vỡ và vết đạn lấy từ pool tạo sẵn, nên mỗi khung hình không cấp phát bộ nhớ mới. Bộ dọn rác (garbage collector) không phải chạy, nên không có những lần khựng ngắn.
- Chuột: dùng sự kiện
pointerrawupdate(báo chuyển động ngay khi chuột gửi về, không đợi khung hình). Các chuyển động được cộng dồn rồi áp dụng một lần mỗi khung và ngay lúc bấm bắn. - HUD: chỉ viết lại số khi giá trị đổi. Độ phân giải tự chọn một lần trong lúc đếm ngược.
- Hiệu ứng sảnh giữ nguyên, nhưng tắt hết khi vào trận (class
body.in-round). - Thêm bộ đếm FPS và lựa chọn độ phân giải 3D (100%, 75%, 50%).
Bài học:
- Hiệu năng đồ họa phụ thuộc vào số lần vẽ (draw call), số điểm ảnh phải tô và rác bộ nhớ mỗi khung hình.
- Đo trước rồi mới sửa.
- Đổi bớt thứ "đẹp nhưng không ai nhìn thấy" để lấy độ mượt.
4.7. Ảnh từ trang khác không tải được trong Discord
- Vấn đề: Activity trong Discord chỉ được tải tài nguyên qua proxy của chính nó (
/.proxy/...). - Cách giải quyết: · Server làm proxy cho valorant-api.com và Valorant wiki. · Cache danh sách 1 ngày, cache ảnh bằng LRU 400 ảnh. · Ảnh lớn được thu nhỏ và nén sang WebP ngay trên server. · Kiểm tra id thật chặt (UUID, hoặc
agentId~slottheo danh sách cho phép) để không ai lợi dụng proxy đi lấy đường dẫn tùy ý.
4.8. Thư viện vẽ ảnh làm sập cả server
- Vấn đề: trong lúc test,
@napi-rs/canvasgặp dữ liệu ảnh hỏng thì làm sập luôn cả tiến trình Node (lỗi từ code native,try/catchkhông bắt được). - Cách giải quyết: trước khi giải mã, kiểm tra chữ ký file (magic bytes): PNG bắt đầu bằng
89 50 4E 47, JPEG bằngFF D8 FF, WebP làRIFF....WEBP. - Bài học: code native không tuân theo cơ chế lỗi của JavaScript. Dữ liệu từ bên ngoài phải được kiểm tra trước khi đưa vào thư viện native.
4.9. Bảng cài đặt tràn khỏi màn hình, nút Lưu không bấm được
- Nguyên nhân thứ nhất: panel không có chiều cao tối đa. Sửa bằng
max-height: calc(100dvh - 32px), hàng lướiminmax(0, 1fr)và cho thân panel cuộn. - Nguyên nhân thứ hai: ở tab Tâm ngắm, code gắn sự kiện cho các nút của tab Chung bị lỗi vì phần tử chưa tồn tại, làm dừng luôn cả đoạn gắn nút Lưu. Sửa bằng cách kiểm tra phần tử có tồn tại không trước khi gắn.
- Bài học: một lỗi JavaScript ở đầu hàm có thể làm "chết" những thứ chẳng liên quan ở cuối hàm.
4.10. Tiêu đề chữ hoa tràn sang cột bên cạnh
- Cách giải quyết: dùng container query (đơn vị
cqi), để cỡ chữ theo bề rộng của chính cột đó chứ không theo cả cửa sổ.
4.11. Chế độ Giữ góc: thùng phải che đầu thật
- Vấn đề: trong 3D, quả cầu nằm trên mặt phẳng mục tiêu. Nếu mặt trước của thùng nằm ngay mặt phẳng đó thì nửa quả cầu lòi ra trước thùng.
- Cách giải quyết: · Đặt mặt trước thùng cách mặt phẳng mục tiêu 0,36 đơn vị về phía người chơi, rồi phóng to theo phối cảnh (tỉ lệ khoảng cách) để nhìn từ mắt người chơi nó che đúng hình chữ nhật mà luật dùng. · Phần thân hộp đặt phía sau mặt phẳng, nên đầu địch luôn được vẽ đè lên nó. · Vết đạn bắn vào thùng nằm trên mặt thùng, không nằm ở tường sau.
- Bài học: phép chiếu phối cảnh, và giữ cho hình vẽ khớp với luật tính điểm.
4.12. Tọa độ callout trên minimap
- Vấn đề: valorant-api cho tọa độ trong game kèm hệ số
xMultiplier,yMultiplier,xScalarToAdd,yScalarToAdd. - Cách giải quyết: · Công thức đúng đảo trục:
x_minimap = y_game × xMultiplier + xScalarToAdd,y_minimap = x_game × yMultiplier + yScalarToAdd. · Kiểm tra bằng mắt: "Attacker Side Spawn" của Split rơi đúng phòng spawn. · Callout tiếng Việt của API bị dịch (Heaven thành "Cầu"), nên luôn lấy tiếng Anh như người chơi vẫn gọi.
4.13. Đố vui "dễ quá", cần ảnh trong map thật
- Vấn đề: API không có ảnh chụp bên trong map.
- Cách giải quyết: · Tìm ra Valorant wiki có ảnh chụp cho từng callout, đặt tên theo callout (
Ascent_A_Main.png). · Scriptscripts/build-mapshots.jsdùng MediaWiki API liệt kê ảnh rồi lọc: chỉ PNG rộng 1920 px; bỏ ảnh so sánh bản vá, thẻ, minimap, nhạc; bỏ khung hình trailer mang tên agent. · Khớp tên file với callout để có tọa độ: 357 ảnh, 274 ảnh có tọa độ. · Repo chỉ lưu danh sách, ảnh lấy qua proxy. · Mảnh cắt bị mờ khi phóng 9 lần ảnh 1280 px, nên giữ ảnh gốc 1920 px và phóng 6 lần.
4.14. Lấy rank Valorant thật
- Vấn đề: tracker.gg không có API công khai cho Valorant. API chính thức của Riot cần đăng ký duyệt bản production.
- Cách giải quyết: · Dùng HenrikDev API (không chính thức, key miễn phí) với header
Authorization, cache 10 phút. · Riot ID lấy qua OAuth scopeconnectionscủa Discord. Tài liệu Discord không ghi loạiriotgames, nên có đường dự phòng: người chơi tự gõTên#TAG. · Nếu người chơi từ chối quyền mới thì đăng nhập lại chỉ vớiidentify, để không ai bị khóa ngoài app.
4.15. Chuột bị kẹt khi Discord không toàn màn hình
- Vấn đề: ở cửa sổ nhỏ, Discord không cho khóa con trỏ (Pointer Lock). Game vẫn xoay theo chuyển động, nhưng con trỏ thật chạm mép khung là hết chuyển động, nên không quay tới được mục tiêu sát mép.
- Cách giải quyết: khi không khóa được thì chuyển sang ngắm bằng con trỏ. · Góc nhìn đứng yên. · Khung nhìn dọc tự mở rộng để chứa trọn cả sân bắn. · Con trỏ hệ thống hiện bằng tâm ngắm của người chơi. · Phát bắn tính bằng cách chiếu tia (raycast) từ vị trí con trỏ lên mặt phẳng mục tiêu. · Có dòng nhắc bật toàn màn hình.
4.16. Lỗi khi triển khai
- Vite build ra bản API cũ: dev API phải khởi động lại mỗi khi sửa luật dùng chung hoặc code server, nếu không trình duyệt nói chuyện với bản cũ (đã gặp với danh sách skin).
- Vercel không tự build một commit. Lúc triển khai tay bằng CLI, lệnh dọn thư mục tạm lỡ xóa mất file liên kết
.vercel, khiến Vercel tạo thêm một dự án thừa.deploy-tmpnối với repo. · Bài học: sau mỗi bước hỏng giữa chừng phải kiểm tra lại trạng thái trước khi chạy tiếp; lệnh dọn dẹp phải chạy sau khi chắc chắn bước chính đã xong. - Cơ sở dữ liệu thay đổi cấu trúc: dùng migration đánh số theo
PRAGMA user_version. Mỗi bản thêm một câuALTER TABLE(cộtriot_id,shared_at), chạy trong transaction, không mất dữ liệu cũ. - Server dùng chung với bot khác: không bao giờ chạy
docker prune, không đụng container và cổng của dịch vụ khác. Chỉ build lại container của chính mình.
---
5. Kỹ thuật máy tính đã học được
5.1. Đồ họa 3D và hiệu năng (three.js, WebGL)
- Camera phối cảnh, góc nhìn ngang cố định 103° rồi tính góc dọc theo tỉ lệ khung hình.
- Thứ tự quay
YXZcho camera FPS, giới hạn góc ngước và cúi (clampView). - Chiếu tia (raycast) từ màn hình vào mặt phẳng để biết điểm ngắm.
- Draw call, gộp hình học (
mergeGeometries), vật liệu dùng chung, object pool. - Vật liệu Basic, Lambert, Phong, Standard: cái nào tốn bao nhiêu.
- Độ phân giải động (pixel ratio),
devicePixelRatio, MSAA. - Vẽ hai cảnh trong một khung hình:
autoClear = false,clearDepth, camera trực giao cho HUD. - Texture: color space sRGB, mipmap, thu nhỏ ảnh qua canvas trước khi đưa lên GPU.
5.2. Xử lý đầu vào
- Pointer Lock API, tùy chọn
unadjustedMovement(bỏ gia tốc chuột của hệ điều hành). pointerrawupdateso vớipointermove, vì sao cộng dồn chuyển động rồi xử lý một lần mỗi khung.- Count của chuột so với pixel CSS, DPI, eDPI, công thức độ nhạy của Valorant.
- Phương án dự phòng khi trình duyệt hoặc iframe từ chối quyền.
5.3. Chống gian lận và tính tất định
- Server có thẩm quyền, chơi lại từ log sự kiện.
- PRNG có seed (mulberry32), lịch xuất hiện mục tiêu chỉ phụ thuộc thời gian và các lần trúng trước đó.
- Phát hiện bất thường theo thống kê (khoảng cách giữa các phát trúng, tỉ lệ trúng, số mẫu chuyển động).
- Seed của thử thách hằng ngày tạo bằng HMAC từ một bí mật, nên không đoán trước được.
5.4. Backend và cơ sở dữ liệu
- SQLite có sẵn trong Node (
node:sqlite), chế độ WAL, index cho truy vấn bảng xếp hạng. - Migration theo
user_version. - API HTTP thuần không dùng framework: định tuyến, gzip cho câu trả lời lớn, header cache (
immutable,no-store). - Proxy và cache: TTL, LRU, trả bản cũ khi nguồn bị lỗi (stale-on-error).
- Kiểm tra đầu vào chặt chẽ: chỉ cho phép giá trị trong danh sách, chuẩn hóa cài đặt.
- Thao tác chỉ làm một lần (idempotent), như "chia sẻ đúng một lần" bằng
UPDATE ... WHERE shared_at IS NULL.
5.5. Discord
- Embedded App SDK:
authorize, OAuth code đổi lấy token chỉ trên server (giữ client secret),authenticate. - Scope OAuth:
identify,connections. Cài cho người dùng so với cài vào server. - Proxy URL của Activity và cách map
/api. - discord.js: lệnh slash, đăng ký lệnh toàn cục so với theo server, embed, gửi file, quyền (Send Messages, Attach Files, Manage Roles), emoji riêng của app.
5.6. Xử lý ảnh
- Vẽ ảnh thẻ trên server bằng
@napi-rs/canvas: font, gradient, chữ tự co cho vừa khung. - Mã hóa WebP, nhận diện định dạng file bằng magic bytes.
- OpenCV: GrabCut, so sánh khung hình, mặt nạ, khử răng cưa ở biên.
- ffmpeg: trích khung hình, đổi kích thước ảnh.
5.7. Dữ liệu từ bên ngoài
- REST API công khai (valorant-api.com), MediaWiki API, OpenAPI spec của HenrikDev.
- Đọc schema để biết chính xác trường dữ liệu thay vì đoán.
- Khớp tên mờ (chuẩn hóa chuỗi) để nối hai nguồn dữ liệu.
- Bản quyền: chính sách nội dung fan của Riot, ảnh trong wiki là ảnh chụp game, chỉ dùng cho dự án miễn phí.
5.8. DevOps
- Docker và docker compose (profile, healthcheck, volume, env_file).
- Tailscale Funnel để đưa dịch vụ ra internet mà không mở cổng router.
- Triển khai bằng
git archive | ssh tar -x. - Vercel tự build từ GitHub, triển khai tay bằng CLI.
- Sao lưu cơ sở dữ liệu, kiểm tra sức khỏe dịch vụ, đọc log.
- Làm việc an toàn trên server dùng chung.
5.9. Kiểm thử
node:test, giả lậpfetchvà Discord để test không cần mạng.- Test chơi lại cho mọi chế độ.
- Kiểm tra kiểu bằng TypeScript (
tsc --noEmit). - Kiểm thử thật bằng trình duyệt tự động: chụp màn hình, đọc console, giả lập cú bấm.
- Giới hạn của trình duyệt tự động: chạy ở 1 FPS nên không đo được độ mượt, cú bấm giả không xin được khóa chuột.
5.10. Thiết kế sản phẩm
- Trang đầu phải trả lời ngay "đây là gì" và "bấm vào đâu"; mọi thứ khác lùi về sau.
- Một màu nhấn duy nhất, chữ hiển thị riêng, góc vát theo phong cách client game.
- Hiệu ứng ở sảnh để gây ấn tượng, trong trận thì chỉ ưu tiên độ mượt.
- Lắng nghe phản hồi và dám bỏ: đầu robot, bối cảnh map, skin tự cắt, 3 bộ giao diện cũ.
---
6. Những việc còn để lại
- Kiểm tra thẻ "Valorant của bạn" với key HenrikDev thật trên Discord.
- Theo dõi xem Discord có trả về tài khoản Riot qua scope
connectionskhông. - Chỉnh lại khoảng điểm của rank Giữ góc khi có dữ liệu thật.
- Xem lại việc các lượt chơi ở chế độ ngắm bằng con trỏ (cửa sổ nhỏ) có nên tính rank không.
- Tìm hiểu vì sao Vercel không tự build commit
499c314.