Serigy-Jev T-Rex

Pesos do classificador ordinal para o Chrome T-Rex Runner (estado do jogo em PT-BR).

Checkpoint raw (sem cola Safe/Unsafe). Sensor: Qwen/Qwen3-0.6B-Base.

Arquivo Função
serigy_jev.safetensors Checkpoint
serigy_jev.json Metadados (model_id, σ, dims)
demo.mp4 Vídeo do experimento (3 lanes)
classe_ordinal Ação
0 abaixar
1 fazer_nada
2 pular

Experimento (vídeo)

Serigy × Jev × Laya — skill pura (unassisted raw), seed 7, time_scale=0.15:

Baixar demo.mp4

Referência: Serigy ≈ Jev ≈ 310 score · Laya ≈ 204.


O que o dinossauro “vê”

Antes de cada decisão o jogo monta um texto em PT (não pixels). A string tem 3 blocos:

Exemplo completo

Jogo do dinossauro. Velocidade 6. Posicao: no chao.
Obstaculo 'cacto grande' a 100 px a frente.
ZONA=JANELA (100 px → pular cacto/passaro baixo ou abaixar passaro alto).
Escolha uma acao: pular, abaixar ou fazer nada.
LONGE/>=160 ou no ar: fazer_nada.
JANELA 70-140 no chao: pular (cacto / passaro baixo) ou abaixar (SO passaro alto).
NUNCA abaixar em cacto nem passaro baixo.

Bloco A — observação (muda a cada frame)

Jogo do dinossauro. Velocidade 6. Posicao: no chao.
Obstaculo 'cacto grande' a 100 px a frente.
ZONA=JANELA (100 px → pular cacto/passaro baixo ou abaixar passaro alto).
Pedaço Significado
Velocidade 6 velocidade atual
Posicao: no chao no chão / no ar / abaixado
Obstaculo '…' a 100 px tipo + distância do obstáculo da frente
ZONA=JANELA distância → VAZIO / LONGE (≥160) / JANELA (70–140) / PERTO / NO_AR

Bloco B — pedido (fixo)

Escolha uma acao: pular, abaixar ou fazer nada.

Bloco C — regras fixas do treino raw (fixas)

LONGE/>=160 ou no ar: fazer_nada.
JANELA 70-140 no chao: pular (cacto / passaro baixo) ou abaixar (SO passaro alto).
NUNCA abaixar em cacto nem passaro baixo.

Neste exemplo: cacto na JANELA → o modelo deve devolver classe_ordinal = 2 (pular).
O raw não inclui Safe/Unsafe nem “melhor ação” do planner.


1. Inferência rápida

pip install serigy-jev
from serigy_jev import SerigyJev

model = SerigyJev("wendellperbar/serigy-trex")
texto = (
    "Jogo do dinossauro. Velocidade 6. Posicao: no chao. "
    "Obstaculo 'cacto grande' a 100 px a frente. "
    "ZONA=JANELA (100 px → pular cacto/passaro baixo ou abaixar passaro alto). "
    "Escolha uma acao: pular, abaixar ou fazer nada. "
    "LONGE/>=160 ou no ar: fazer_nada. "
    "JANELA 70-140 no chao: pular (cacto / passaro baixo) "
    "ou abaixar (SO passaro alto). "
    "NUNCA abaixar em cacto nem passaro baixo."
)
out = model.predict(texto)
print(out["classe_ordinal"], out["score_final"], out["probabilidades"])

2. Mini-arena autocontida (Serigy × Jev × Laya)

Script abaixo: física simplificada do T-Rex, prompt raw no mesmo formato do treino, e três jogadores. Não depende de nenhum outro código além dos pacotes listados.

Dependências

pip install serigy-jev typesafe-sdk laya
export JEV_API_KEY=...   # ou TYPESAFE_API_KEY — só para o player Jev

Laya e Jev são opcionais: sem chave/pacote, o script pula a lane.

Protocolo do experimento

Parâmetro Valor
prompt raw (zonas Chrome; sem Safe/Unsafe)
time_scale 0.15 (ablacão de latência)
seed 7
decide a cada 6 frames
cacto / pássaro baixo na JANELA (70–140 px) pular
pássaro alto na JANELA abaixar
LONGE (≥160) ou no ar fazer_nada

Script completo

Salve como mini_arena_trex.py e rode python mini_arena_trex.py.

"""Mini-arena T-Rex: Serigy (Hub) × Jev (TypeSafe) × Laya — autocontida."""

from __future__ import annotations

import math
import os
import random
import time
from dataclasses import dataclass, field

# ---------------------------------------------------------------------------
# Física mínima (60 FPS, y para cima a partir do chão)
# ---------------------------------------------------------------------------

FPS = 60
GRAVIDADE = 0.6
VEL_PULO = 10.0
DINO_X, DINO_W, DINO_H, DINO_H_DUCK = 50.0, 44.0, 47.0, 25.0
VEL0, VEL_MAX, ACEL = 6.0, 13.0, 0.001
JANELA_MIN, JANELA_MAX, LONGE = 70.0, 140.0, 160.0

# tipo → (w, h, y_base, nome_pt)
OBS = {
    "cacto": (25.0, 50.0, 0.0, "cacto grande"),
    "cacto_x3": (75.0, 50.0, 0.0, "tres cactos grandes"),
    "passaro_alto": (46.0, 40.0, 40.0, "passaro alto"),
    "passaro_baixo": (46.0, 40.0, 10.0, "passaro baixo"),
}


@dataclass
class Obstaculo:
    tipo: str
    x: float
    y: float
    w: float
    h: float


@dataclass
class Game:
    seed: int
    time_scale: float = 0.15
    frame: int = 0
    vel: float = VEL0
    score: float = 0.0
    dino_y: float = 0.0
    dino_vy: float = 0.0
    abaixado: bool = False
    vivo: bool = True
    obstaculos: list[Obstaculo] = field(default_factory=list)
    _next_x: float = 600.0
    _rng: random.Random = field(default_factory=random.Random)

    def __post_init__(self) -> None:
        self._rng = random.Random(self.seed)
        self._spawn()

    @property
    def no_ar(self) -> bool:
        return self.dino_y > 0.5

    def _spawn(self) -> None:
        tipos = ["cacto", "cacto", "cacto_x3", "passaro_alto", "passaro_baixo"]
        # curso simples: alterna tipos com gap generoso
        tipo = tipos[self._rng.randrange(len(tipos))]
        if self.vel < 7.0 and tipo.startswith("passaro"):
            tipo = "cacto"
        w, h, y, _ = OBS[tipo]
        gap = w * self.vel + 120.0 + self._rng.random() * 80.0
        self._next_x = max(self._next_x, 600.0) + gap
        self.obstaculos.append(
            Obstaculo(tipo, self._next_x, y, w, h)
        )

    def frente(self) -> Obstaculo | None:
        cand = [o for o in self.obstaculos if o.x + o.w > DINO_X]
        return min(cand, key=lambda o: o.x) if cand else None

    def dist(self) -> float | None:
        o = self.frente()
        return None if o is None else max(0.0, o.x - DINO_X)

    def step(self, acao: str) -> None:
        if not self.vivo:
            return
        ts = max(1e-6, self.time_scale)
        scroll = self.vel * ts

        # tecla
        if acao == "pular" and not self.no_ar:
            self.dino_vy = VEL_PULO
            self.abaixado = False
        elif acao == "abaixar" and not self.no_ar:
            self.abaixado = True
        elif acao == "fazer_nada":
            if not self.no_ar:
                self.abaixado = False

        # física dino
        if self.no_ar or self.dino_vy > 0:
            self.dino_y += self.dino_vy
            self.dino_vy -= GRAVIDADE
            if self.dino_y <= 0:
                self.dino_y, self.dino_vy = 0.0, 0.0

        # scroll obstáculos
        for o in self.obstaculos:
            o.x -= scroll
        self.obstaculos = [o for o in self.obstaculos if o.x + o.w > -20]
        if not self.obstaculos or self.obstaculos[-1].x < 500:
            self._spawn()

        # colisão AABB
        h = DINO_H_DUCK if self.abaixado and not self.no_ar else DINO_H
        dx0, dy0, dx1, dy1 = DINO_X, self.dino_y, DINO_X + DINO_W, self.dino_y + h
        for o in self.obstaculos:
            if dx0 < o.x + o.w and dx1 > o.x and dy0 < o.y + o.h and dy1 > o.y:
                self.vivo = False
                break

        self.frame += 1
        self.score += self.vel * ts * 0.1
        self.vel = min(VEL_MAX, self.vel + ACEL * ts)


# ---------------------------------------------------------------------------
# Prompt raw (mesmo contrato do treino)
# ---------------------------------------------------------------------------

def formatar_estado(g: Game) -> str:
    if g.no_ar:
        pos = "Posicao: no ar."
    elif g.abaixado:
        pos = "Posicao: abaixado."
    else:
        pos = "Posicao: no chao."

    o = g.frente()
    dist = g.dist()
    if o is None or dist is None:
        desc = "Nenhum obstaculo proximo (mais de 300 px)."
        zona = "ZONA=VAZIO (fazer_nada)."
    else:
        nome = OBS[o.tipo][3]
        desc = f"Obstaculo '{nome}' a {dist:.0f} px a frente."
        if g.no_ar:
            zona = "ZONA=NO_AR (so fazer_nada)."
        elif dist >= LONGE:
            zona = f"ZONA=LONGE ({dist:.0f} px → fazer_nada)."
        elif dist >= JANELA_MIN:
            zona = (
                f"ZONA=JANELA ({dist:.0f} px → pular cacto/passaro baixo "
                "ou abaixar passaro alto)."
            )
        else:
            zona = f"ZONA=PERTO ({dist:.0f} px → na duvida fazer_nada)."

    return (
        f"Jogo do dinossauro. Velocidade {g.vel:.0f}. {pos} {desc} {zona} "
        "Escolha uma acao: pular, abaixar ou fazer nada. "
        "LONGE/>=160 ou no ar: fazer_nada. "
        "JANELA 70-140 no chao: pular (cacto / passaro baixo) "
        "ou abaixar (SO passaro alto). "
        "NUNCA abaixar em cacto nem passaro baixo."
    )


def acao_cinematica(proposta: str, g: Game) -> str:
    """Decide cedo; só executa pular/abaixar na JANELA (timing)."""
    if g.no_ar:
        return "fazer_nada"
    dist = g.dist()
    if dist is None:
        return "fazer_nada"
    if proposta == "fazer_nada":
        return "fazer_nada"
    if dist > JANELA_MAX:
        return "fazer_nada"  # ainda longe — espera
    if dist < JANELA_MIN and proposta == "pular":
        return "fazer_nada"  # passou a janela de takeoff
    return proposta


# ---------------------------------------------------------------------------
# Players
# ---------------------------------------------------------------------------

INSTRUCOES = (
    "Skill pura (sem cola), timing Chrome: "
    "(1) ZONA=NO_AR → fazer_nada. "
    "(2) ZONA=LONGE (>=160) → fazer_nada. "
    "(3) ZONA=JANELA (70-140) no chao: pular (cacto/passaro baixo) "
    "ou abaixar (SO passaro alto). "
    "(4) NUNCA abaixar em cacto nem passaro baixo."
)

CRITERIOS = {
    "pular": (
        "Pular em cacto / tres cactos / passaro baixo na ZONA=JANELA. "
        "Nunca em passaro alto; nunca em LONGE."
    ),
    "abaixar": (
        "Abaixar SOMENTE em passaro alto na ZONA=JANELA. "
        "VETO: nunca abaixar em cacto."
    ),
    "fazer_nada": (
        "Fazer nada em LONGE, vazio, no ar, ou fora da janela."
    ),
}


def decidir_serigy(model, texto: str) -> str:
    out = model.predict(texto)
    return {0: "abaixar", 1: "fazer_nada", 2: "pular"}[
        int(out["classe_ordinal"])
    ]


def decidir_jev(client, Choice, texto: str) -> str:
    q = {
        "acao": Choice(instructions=INSTRUCOES, criteria=CRITERIOS),
    }
    result = client.system_one(texto, q, model="jev-latest")
    key = None
    if hasattr(result, "choices") and "acao" in result.choices:
        ans = result.choices["acao"]
        key = getattr(ans, "choice", None) or getattr(ans, "key", None)
    if key not in {"pular", "abaixar", "fazer_nada"}:
        key = "fazer_nada"
    return key


def decidir_laya(router, texto: str) -> str:
    questions = {
        "acao": {
            "type": "choice",
            "instructions": INSTRUCOES
            + " VETO: cacto → nunca abaixar.",
            "criteria": CRITERIOS,
        }
    }
    result = router.predict(texto, questions, model="multilingual")
    key = None
    if hasattr(result, "choices") and "acao" in result.choices:
        ans = result.choices["acao"]
        key = getattr(ans, "choice", None) or getattr(ans, "key", None)
    elif isinstance(result, dict):
        key = (result.get("acao") or {}).get("choice")
    if key not in {"pular", "abaixar", "fazer_nada"}:
        key = "fazer_nada"
    return key


# ---------------------------------------------------------------------------
# Loop
# ---------------------------------------------------------------------------

def rodar(nome: str, decidir, seconds: float = 20.0, seed: int = 7) -> dict:
    g = Game(seed=seed, time_scale=0.15)
    max_frames = int(seconds / g.time_scale * FPS)
    decide_every = 6
    proposta = "fazer_nada"
    decisoes = 0
    t_lat = []

    while g.vivo and g.frame < max_frames:
        if g.frame % decide_every == 0:
            texto = formatar_estado(g)
            t0 = time.perf_counter()
            proposta = decidir(texto)
            t_lat.append((time.perf_counter() - t0) * 1000.0)
            decisoes += 1
        acao = acao_cinematica(proposta, g)
        g.step(acao)

    lat = sorted(t_lat)
    p50 = lat[len(lat) // 2] if lat else 0.0
    return {
        "player": nome,
        "score": round(g.score, 1),
        "frames": g.frame,
        "vivo": g.vivo,
        "decisoes": decisoes,
        "lat_p50_ms": round(p50, 1),
    }


def main() -> None:
    from serigy_jev import SerigyJev

    serigy = SerigyJev("wendellperbar/serigy-trex", warm=True)
    resultados = [
        rodar("serigy", lambda t: decidir_serigy(serigy, t)),
    ]

    key = (
        os.environ.get("JEV_API_KEY")
        or os.environ.get("TYPESAFE_API_KEY")
        or ""
    ).strip()
    if key:
        os.environ.setdefault("TYPESAFE_API_KEY", key)
        from typesafe_sdk import Choice, TypeSafeClient

        jev = TypeSafeClient()
        resultados.append(
            rodar("jev", lambda t: decidir_jev(jev, Choice, t))
        )
    else:
        print("skip jev (defina JEV_API_KEY ou TYPESAFE_API_KEY)")

    try:
        from laya import Router

        laya = Router(preload=True, default="multilingual")
        resultados.append(
            rodar("laya", lambda t: decidir_laya(laya, t))
        )
    except ImportError:
        print("skip laya (pip install laya)")

    for r in resultados:
        print(r)


if __name__ == "__main__":
    main()

Cada player vê o mesmo seed (mesmo curso). Ajuste seconds no rodar(...) para partidas mais longas.

O que o Serigy faz aqui

  1. O jogo monta o texto do estado (velocidade, posição, obstáculo, zona).
  2. SerigyJev.predict(texto) → classe_ordinal {0,1,2}.
  3. A cinemática só aplica pular/abaixar dentro da JANELA 70–140 px.

É o mesmo contrato de entrada/saída do checkpoint deste Hub.

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for wendellperbar/serigy-trex

Finetuned
(723)
this model