A polished GAN beauty demo can create momentum—and an uncomfortable question for the engineers in the room:
Who decides whether this is actually ready to ship?
The model can generate an impressive effect. It cannot decide whether the visual result is respectful, whether the extra workload is acceptable on your supported devices, or whether an incomplete test run should count as evidence.
That uncertainty is not a sign that your role has become less relevant. It is a sign that the difficult work has moved. Instead of asking only whether the effect works, the team needs an explicit release contract covering performance, visual review, consent, failure behavior, and ownership.
In this tutorial, we will build that contract as a small TypeScript evidence gate for a Tencent RTC Beauty AR scenario. The gate will:
- Compare a GAN candidate with a baseline on the same device.
- Reject incomplete or incomparable measurements.
- Keep performance evidence separate from human visual approval.
- Produce one of four explicit release states.
- Require a verified baseline fallback before approving the candidate.
- Prevent a presentation or screenshot from becoming accidental release approval.
The code does not invent Tencent RTC SDK methods. It creates an application-owned test and release layer that you can connect to your actual Beauty AR integration.
The release question we are answering
Assume a team wants to add an optional GAN-based appearance effect to a live calling or streaming experience.
Tencent RTC Beauty AR supports real-time capabilities including beauty filters, makeup, stickers, virtual backgrounds, avatars, gesture recognition, and image or video enhancement. The product overview is here:
https://trtc.io/document/beauty-ar-overview
The low-end device optimization guide recommends treating effects as workloads. Depending on device capability, that can mean selecting an appropriate performance mode, controlling resolution and frame rate, or disabling expensive segmentation, 3D, and GAN effects:
https://trtc.io/document/66968
Those recommendations do not give every application one universal passing score. A live commerce stream, a casual call, and a recorded creator workflow may make different trade-offs.
Our application therefore defines its own release question:
Does the candidate retain enough of the baseline's measured rendering performance on every device in the declared release scope, pass visual review, preserve an off control, and recover to a verified baseline if activation fails?
The threshold belongs to the product team. It is not a Tencent RTC performance guarantee.
Turn the demo into an evidence artifact
A useful test run needs more than passed: true. It should identify:
- The application and effect build.
- The physical device or reproducible device class.
- Whether the baseline or candidate was active.
- The test scene and duration.
- Observed frame-rate samples from application instrumentation.
- Renderer errors and thermal warnings available to the host application.
- Whether the user-facing off control was tested.
- The human review result.
- Whether the baseline fallback was verified.
Avoid retaining face images merely because the test harness can capture them. Performance samples and a reviewer decision may be sufficient. If visual artifacts must be stored, define participant consent, access, and deletion rules before recording.
Create the test project
mkdir beauty-release-gate cd beauty-release-gate npm init -y npm install -D typescript vitest @types/node npx tsc --init mkdir src test Update package.json:
{ "scripts": { "test": "vitest run", "test:watch": "vitest" } } Create src/model.ts:
export type Profile = "baseline" | "gan-candidate"; export type RunState = | "planned" | "running" | "complete" | "cancelled" | "invalid"; export type ReviewDecision = | "pending" | "accepted" | "rejected"; export interface TestRun { id: string; deviceId: string; deviceClass: string; appBuild: string; effectBuild: string; sceneId: string; profile: Profile; state: RunState; // Values collected by the host application's instrumentation. observedFps: number[]; rendererErrors: string[]; thermalWarnings: number; offControlVerified: boolean; fallbackVerified: boolean; review: ReviewDecision; } export interface GatePolicy { requiredDeviceIds: string[]; minimumFpsRetention: number; requireNoRendererErrors: boolean; requireNoThermalWarnings: boolean; } export type GateState = | "collecting-evidence" | "awaiting-human-review" | "approved" | "rejected"; export interface GateResult { state: GateState; reasons: string[]; } There are two deliberate separations here.
First, complete does not mean accepted. A technically complete run can still fail either performance policy or human review.
Second, the baseline and candidate are separate records. That prevents a candidate measured on one device or scene from being compared with an unrelated baseline.
Make invalid evidence impossible to overlook
Create src/gate.ts:
import { GatePolicy, GateResult, TestRun, } from "./model.js"; function median(values: number[]): number | undefined { if (values.length === 0) return undefined; const sorted = [...values].sort((a, b) => a - b); const middle = Math.floor(sorted.length / 2); return sorted.length % 2 === 0 ? (sorted[middle - 1] + sorted[middle]) / 2 : sorted[middle]; } function comparable(a: TestRun, b: TestRun): boolean { return ( a.deviceId === b.deviceId && a.appBuild === b.appBuild && a.effectBuild === b.effectBuild && a.sceneId === b.sceneId ); } export function evaluateGate( runs: TestRun[], policy: GatePolicy, ): GateResult { const reasons: string[] = []; let waitingForReview = false; for (const deviceId of policy.requiredDeviceIds) { const baseline = runs.find( run => run.deviceId === deviceId && run.profile === "baseline", ); const candidate = runs.find( run => run.deviceId === deviceId && run.profile === "gan-candidate", ); if (!baseline || !candidate) { reasons.push(`${deviceId}: baseline or candidate run is missing`); continue; } if (baseline.state !== "complete" || candidate.state !== "complete") { reasons.push(`${deviceId}: both runs must be complete`); continue; } if (!comparable(baseline, candidate)) { reasons.push(`${deviceId}: runs use different builds or scenes`); continue; } const baselineFps = median(baseline.observedFps); const candidateFps = median(candidate.observedFps); if (baselineFps === undefined || candidateFps === undefined) { reasons.push(`${deviceId}: frame-rate evidence is missing`); continue; } const retention = candidateFps / baselineFps; if (retention < policy.minimumFpsRetention) { reasons.push( `${deviceId}: FPS retention ${retention.toFixed(2)} is below ` + `${policy.minimumFpsRetention.toFixed(2)}`, ); } if ( policy.requireNoRendererErrors && candidate.rendererErrors.length > 0 ) { reasons.push(`${deviceId}: candidate reported renderer errors`); } if ( policy.requireNoThermalWarnings && candidate.thermalWarnings > 0 ) { reasons.push(`${deviceId}: candidate reported thermal warnings`); } if (!candidate.offControlVerified) { reasons.push(`${deviceId}: user-facing off control was not verified`); } if (!candidate.fallbackVerified) { reasons.push(`${deviceId}: baseline fallback was not verified`); } if (candidate.review === "rejected") { reasons.push(`${deviceId}: human visual review rejected the result`); } else if (candidate.review === "pending") { waitingForReview = true; } } if (reasons.length > 0) { const incomplete = reasons.some(reason => reason.includes("missing") || reason.includes("must be complete") || reason.includes("different builds"), ); return { state: incomplete ? "collecting-evidence" : "rejected", reasons, }; } if (waitingForReview) { return { state: "awaiting-human-review", reasons: ["Performance evidence passed; visual review is pending"], }; } return { state: "approved", reasons: [], }; } This gate treats missing data as missing data—not as a pass and not automatically as a product failure.
That distinction matters:
- Collecting evidence means the test is incomplete or incomparable.
- Awaiting human review means the measurable policy passed, but a person still needs to assess the result.
- Rejected means complete evidence violated a release rule.
- Approved means every declared requirement passed.
A team can now discuss the policy instead of arguing over whether a demo “felt smooth.”
Reproduce the disputed cases
Create test/gate.test.ts:
import { describe, expect, it } from "vitest"; import { evaluateGate } from "../src/gate.js"; import type { GatePolicy, TestRun } from "../src/model.js"; const policy: GatePolicy = { requiredDeviceIds: ["device-a"], // Example application policy, not a product guarantee. minimumFpsRetention: 0.85, requireNoRendererErrors: true, requireNoThermalWarnings: true, }; function run( profile: TestRun["profile"], overrides: Partial<TestRun> = {}, ): TestRun { return { id: `${profile}-run`, deviceId: "device-a", deviceClass: "release-matrix-entry-1", appBuild: "app-42", effectBuild: "effect-7", sceneId: "indoor-steady-light", profile, state: "complete", observedFps: profile === "baseline" ? [30, 30, 29] : [27, 27, 26], rendererErrors: [], thermalWarnings: 0, offControlVerified: true, fallbackVerified: true, review: profile === "baseline" ? "accepted" : "pending", ...overrides, }; } describe("Beauty AR release gate", () => { it("does not turn performance evidence into visual approval", () => { const result = evaluateGate( [run("baseline"), run("gan-candidate")], policy, ); expect(result.state).toBe("awaiting-human-review"); }); it("rejects a candidate that falls below the team's retention rule", () => { const candidate = run("gan-candidate", { observedFps: [20, 20, 19], review: "accepted", }); const result = evaluateGate( [run("baseline"), candidate], policy, ); expect(result.state).toBe("rejected"); expect(result.reasons[0]).toContain("FPS retention"); }); it("does not compare different effect builds", () => { const candidate = run("gan-candidate", { effectBuild: "effect-8", review: "accepted", }); const result = evaluateGate( [run("baseline"), candidate], policy, ); expect(result.state).toBe("collecting-evidence"); }); it("requires a tested fallback before approval", () => { const candidate = run("gan-candidate", { review: "accepted", fallbackVerified: false, }); const result = evaluateGate( [run("baseline"), candidate], policy, ); expect(result.state).toBe("rejected"); expect(result.reasons[0]).toContain("fallback"); }); it("approves only complete technical and human evidence", () => { const candidate = run("gan-candidate", { review: "accepted", }); const result = evaluateGate( [run("baseline"), candidate], policy, ); expect(result).toEqual({ state: "approved", reasons: [] }); }); }); Run it:
npm test The important test is not merely the happy path. It is the case where the measurements look acceptable but review is still pending. That is where an enthusiastic presentation can otherwise become an implicit approval.
Collect paired runs without biasing the result
A comparison is only useful when the runs are meaningfully comparable. Use this workflow for each physical device in your release matrix:
- Confirm participant consent before enabling any appearance effect or recording visual evidence.
- Record the application build, effect build, device identifier, and test scene.
- Run the baseline for the configured duration.
- Allow the device to return to the team's defined starting condition.
- Run the GAN candidate in the same scene.
- Repeat in the opposite order to expose warm-up or thermal-order bias.
- Exercise the visible off control during the candidate run.
- Force candidate activation to fail and verify restoration of the baseline.
- Submit the visual result for human review.
- Evaluate the complete device matrix rather than selecting the best run.
Lighting, camera movement, background complexity, and multiple effects can change the workload. Give each important combination a distinct sceneId instead of silently mixing its samples with an easier scene.
Do not use one median to hide an obviously unstable run. The example gate is intentionally small. A production policy can add duration requirements, variability checks, or sustained-window analysis, provided those rules are documented and tested.
Connect the gate to Beauty AR without guessing SDK APIs
The gate should not directly call a product-specific method. Put a narrow adapter around the Beauty AR integration used by your application:
export interface BeautyRendererPort { applyBaseline(): Promise<void>; applyGanCandidate(): Promise<void>; } export type RuntimeEffectState = | "off" | "applying-candidate" | "candidate-active" | "restoring-baseline" | "baseline-active" | "unavailable"; export interface ActivationResult { state: RuntimeEffectState; reason?: string; } export async function activateApprovedCandidate( renderer: BeautyRendererPort, releaseState: "approved" | "not-approved", consent: boolean, ): Promise<ActivationResult> { if (!consent) { return { state: "off", reason: "User consent is not active" }; } if (releaseState !== "approved") { await renderer.applyBaseline(); return { state: "baseline-active", reason: "GAN candidate is not approved for this release scope", }; } try { await renderer.applyGanCan...