```ts
export function runTillSession(events: (string | number)[][], prices: Record<string, number>): { state: string; items: [string, number][]; total: number; paid: number; change: number } {
    const initialState = {
        state: "open",
        items: [] as [string, number][],
        total: 0,
        paid: 0,
        change: 0
    };

    let session = { ...initialState };

    for (const event of events) {
        if (session.state === "paid" || session.state === "cancelled") {
            throw new Error("Event after session is paid or cancelled");
        }

        switch (event[0]) {
            case "scan":
                const itemName = event[1] as string;
                if (!prices[itemName]) {
                    throw new Error(`Unknown item: ${itemName}`);
                }
                if (session.state !== "open") {
                    throw new Error("Illegal action in current state");
                }
                session.items.push([itemName, (session.items.find(([name]) => name === itemName)?.[1] || 0) + 1]);
                break;
            case "void":
                const voidItemName = event[1] as string;
                if (!session.items.some(([name]) => name === voidItemName)) {
                    throw new Error(`Cannot void item not in cart: ${voidItemName}`);
                }
                if (session.state !== "open") {
                    throw new Error("Illegal action in current state");
                }
                session.items = session.items.filter(([name]) => name !== voidItemName);
                break;
            case "close":
                if (session.state !== "open" || session.items.length === 0) {
                    throw new Error("Illegal close event");
                }
                session.state = "payment";
                session.total = session.items.reduce((acc, [_, quantity]) => acc + prices[quantity > 1 ? `${item}_x${quantity}` : item] * quantity, 0);
                break;
            case "pay":
                const payAmount = event[1] as number;
                if (typeof payAmount !== "number" || payAmount <= 0) {
                    throw new Error("Invalid pay amount");
                }
                if (session.state !== "payment") {
                    throw new Error("Illegal action in current state");
                }
                session.paid += payAmount;
                session.change = Math.max(session.paid - session.total, 0);
                session.state = "paid";
                break;
            case "cancel":
                session.state = "cancelled";
                break;
            default:
                throw new Error(`Unknown action: ${event[0]}`);
        }
    }

    return {
        state: session.state,
        items: session.items.sort((a, b) => a[0].localeCompare(b[0])),
        total: session.total,
        paid: session.paid,
        change: session.change
    };
}
```