Skip to main content
Glama

ot_travel

Plan supplies and choose a travel pace to manage food consumption and health changes during a 3-day trail. Random events add further gains or losses.

Instructions

Hit the trail for 3 days. Always costs food and may affect health — plan supplies before calling. Costs: easy=-6 food, +9hp each; steady=-9 food, 0hp change; hard=-12 food, -21hp each, -12hp pet. Random events add further gains/losses on top. Narrate what happens to the human dramatically.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
paceYesTravel pace — easy (15 mi/day, -6 food, +9hp), steady (22 mi/day, -9 food, no health change), hard (32 mi/day, -12 food, -21hp, -12hp pet)

Implementation Reference

  • The ot_travel tool handler: simulates 3 days of travel at a given pace (easy/steady/hard). Manages food consumption, health changes, pet wear on hard pace, random events, weather changes, stop arrivals, river crossings, and game-over checks. Returns a narrative summary and updated game state.
    server.tool(
      "ot_travel",
      "Hit the trail for 3 days. Always costs food and may affect health — plan supplies before calling. Costs: easy=-6 food, +9hp each; steady=-9 food, 0hp change; hard=-12 food, -21hp each, -12hp pet. Random events add further gains/losses on top. Narrate what happens to the human dramatically.",
      {
        pace: z.enum(["easy", "steady", "hard"]).describe("Travel pace — easy (15 mi/day, -6 food, +9hp), steady (22 mi/day, -9 food, no health change), hard (32 mi/day, -12 food, -21hp, -12hp pet)"),
      },
      async ({ pace }) => {
        if (!otGame) return { content: [{ type: "text", text: "No journey in progress. Call ot_new_game to begin." }], isError: true };
        if (otGame.status === "won" || otGame.status === "game_over") return { content: [{ type: "text", text: `The journey is over. Call ot_new_game to start again.` }], isError: true };
        if (otGame.pendingRiver) return { content: [{ type: "text", text: `You're at a river. Choose a crossing method with ot_cross_river first.\n\n${renderState(otGame)}` }], isError: true };
    
        const milesPerDay  = pace === "easy" ? 15 : pace === "steady" ? 22 : 32;
        const healthPerDay = pace === "easy" ?  3 : pace === "steady" ?  0 : -7;
        const foodPerDay   = pace === "easy" ?  2 : pace === "steady" ?  3 :  4;
        const daysOfTravel = 3;
        const narratives: string[] = [];
        const foodBefore = otGame.supplies.food;
    
        for (let d = 0; d < daysOfTravel; d++) {
          otGame.day++;
          otGame.mile = Math.min(TOTAL_MILES, otGame.mile + milesPerDay);
          otGame.supplies.food = Math.max(0, otGame.supplies.food - foodPerDay);
    
          // Starvation
          if (otGame.supplies.food <= 0) {
            otGame.player.health    = clamp(otGame.player.health - 10, 0, 100);
            otGame.companion.health = clamp(otGame.companion.health - 10, 0, 100);
            narratives.push(`No food. Hunger is taking its toll.`);
            log(otGame, "No provisions. Everyone weakening.");
          }
    
          // Pace health
          if (healthPerDay !== 0) {
            otGame.player.health    = clamp(otGame.player.health + healthPerDay, 0, 100);
            otGame.companion.health = clamp(otGame.companion.health + healthPerDay, 0, 100);
          }
          // Pets wear down on hard pace too
          if (pace === "hard" && otGame.pet.alive) {
            otGame.pet.health = clamp(otGame.pet.health - 4, 0, 100);
            if (otGame.pet.health <= 0) otGame.pet.alive = false;
          }
    
          // Random event
          if (Math.random() < 0.65) {
            const ev = rollEvent(otGame);
            applyEvent(otGame, ev);
            narratives.push(ev.text);
            log(otGame, ev.text.slice(0, 80) + (ev.text.length > 80 ? "…" : ""));
          }
    
          // Weather shift
          if (Math.random() < 0.25) otGame.weather = pick(WEATHER_OPTIONS);
    
          // Check new stop arrival
          for (let i = otGame.currentStopIndex + 1; i < otGame.trailStops.length; i++) {
            if (otGame.mile >= otGame.trailStops[i].mile) {
              otGame.currentStopIndex = i;
              const stop = otGame.trailStops[i];
              narratives.push(`You've reached ${stop.name}.`);
              log(otGame, `Arrived at ${stop.name}.`);
    
              if (stop.type === "destination") {
                otGame.status = "won";
              } else if (stop.type === "river") {
                otGame.status    = "river_crossing";
                otGame.pendingRiver = true;
              } else {
                otGame.status = "at_stop";
              }
              break;
            }
          }
    
          // Death check
          if (otGame.player.health <= 0 && otGame.companion.health <= 0) {
            otGame.status = "game_over";
            narratives.push("Both travelers have fallen. The road claims two more who sought the Well.");
            break;
          }
    
          if (otGame.status === "won" || (otGame.status as string) === "game_over" || otGame.pendingRiver) break;
        }
    
        if (otGame.status !== "at_stop" && otGame.status !== "river_crossing" &&
            otGame.status !== "won" && otGame.status !== "game_over") {
          otGame.status = "traveling";
        }
    
        const travelFoodCost = foodPerDay * daysOfTravel;
        const netFoodChange  = otGame.supplies.food - foodBefore;
        const eventFoodNet   = netFoodChange + travelFoodCost;  // positive = events gave food overall
    
        const summary = [
          `━━ ${daysOfTravel} days on the trail (${pace} pace) ━━`,
          `Travel cost: -${travelFoodCost} food, +${milesPerDay * daysOfTravel} miles`,
          eventFoodNet !== 0
            ? `Events: ${eventFoodNet > 0 ? "+" : ""}${eventFoodNet} food (net)`
            : `Events: no net food change`,
          `Food: ${foodBefore} → ${otGame.supplies.food}`,
        ].join("  |  ");
    
        const body = narratives.map((n, i) => `Day ${otGame!.day - (daysOfTravel - 1 - i)}: ${n}`).join("\n\n");
        return {
          content: [{
            type: "text",
            text: `${summary}\n\n${body}\n\n${renderState(otGame)}`,
          }],
        };
      }
    );
  • Input schema for ot_travel: accepts 'pace' parameter as a string enum with values 'easy', 'steady', or 'hard'.
    {
      pace: z.enum(["easy", "steady", "hard"]).describe("Travel pace — easy (15 mi/day, -6 food, +9hp), steady (22 mi/day, -9 food, no health change), hard (32 mi/day, -12 food, -21hp, -12hp pet)"),
    },
  • The tool is registered via server.tool() with the name 'ot_travel' inside the registerOregonTrailTools function (line 687).
    server.tool(
      "ot_travel",
      "Hit the trail for 3 days. Always costs food and may affect health — plan supplies before calling. Costs: easy=-6 food, +9hp each; steady=-9 food, 0hp change; hard=-12 food, -21hp each, -12hp pet. Random events add further gains/losses on top. Narrate what happens to the human dramatically.",
      {
        pace: z.enum(["easy", "steady", "hard"]).describe("Travel pace — easy (15 mi/day, -6 food, +9hp), steady (22 mi/day, -9 food, no health change), hard (32 mi/day, -12 food, -21hp, -12hp pet)"),
      },
      async ({ pace }) => {
        if (!otGame) return { content: [{ type: "text", text: "No journey in progress. Call ot_new_game to begin." }], isError: true };
        if (otGame.status === "won" || otGame.status === "game_over") return { content: [{ type: "text", text: `The journey is over. Call ot_new_game to start again.` }], isError: true };
        if (otGame.pendingRiver) return { content: [{ type: "text", text: `You're at a river. Choose a crossing method with ot_cross_river first.\n\n${renderState(otGame)}` }], isError: true };
    
        const milesPerDay  = pace === "easy" ? 15 : pace === "steady" ? 22 : 32;
        const healthPerDay = pace === "easy" ?  3 : pace === "steady" ?  0 : -7;
        const foodPerDay   = pace === "easy" ?  2 : pace === "steady" ?  3 :  4;
        const daysOfTravel = 3;
        const narratives: string[] = [];
        const foodBefore = otGame.supplies.food;
    
        for (let d = 0; d < daysOfTravel; d++) {
          otGame.day++;
          otGame.mile = Math.min(TOTAL_MILES, otGame.mile + milesPerDay);
          otGame.supplies.food = Math.max(0, otGame.supplies.food - foodPerDay);
    
          // Starvation
          if (otGame.supplies.food <= 0) {
            otGame.player.health    = clamp(otGame.player.health - 10, 0, 100);
            otGame.companion.health = clamp(otGame.companion.health - 10, 0, 100);
            narratives.push(`No food. Hunger is taking its toll.`);
            log(otGame, "No provisions. Everyone weakening.");
          }
    
          // Pace health
          if (healthPerDay !== 0) {
            otGame.player.health    = clamp(otGame.player.health + healthPerDay, 0, 100);
            otGame.companion.health = clamp(otGame.companion.health + healthPerDay, 0, 100);
          }
          // Pets wear down on hard pace too
          if (pace === "hard" && otGame.pet.alive) {
            otGame.pet.health = clamp(otGame.pet.health - 4, 0, 100);
            if (otGame.pet.health <= 0) otGame.pet.alive = false;
          }
    
          // Random event
          if (Math.random() < 0.65) {
            const ev = rollEvent(otGame);
            applyEvent(otGame, ev);
            narratives.push(ev.text);
            log(otGame, ev.text.slice(0, 80) + (ev.text.length > 80 ? "…" : ""));
          }
    
          // Weather shift
          if (Math.random() < 0.25) otGame.weather = pick(WEATHER_OPTIONS);
    
          // Check new stop arrival
          for (let i = otGame.currentStopIndex + 1; i < otGame.trailStops.length; i++) {
            if (otGame.mile >= otGame.trailStops[i].mile) {
              otGame.currentStopIndex = i;
              const stop = otGame.trailStops[i];
              narratives.push(`You've reached ${stop.name}.`);
              log(otGame, `Arrived at ${stop.name}.`);
    
              if (stop.type === "destination") {
                otGame.status = "won";
              } else if (stop.type === "river") {
                otGame.status    = "river_crossing";
                otGame.pendingRiver = true;
              } else {
                otGame.status = "at_stop";
              }
              break;
            }
          }
    
          // Death check
          if (otGame.player.health <= 0 && otGame.companion.health <= 0) {
            otGame.status = "game_over";
            narratives.push("Both travelers have fallen. The road claims two more who sought the Well.");
            break;
          }
    
          if (otGame.status === "won" || (otGame.status as string) === "game_over" || otGame.pendingRiver) break;
        }
    
        if (otGame.status !== "at_stop" && otGame.status !== "river_crossing" &&
            otGame.status !== "won" && otGame.status !== "game_over") {
          otGame.status = "traveling";
        }
    
        const travelFoodCost = foodPerDay * daysOfTravel;
        const netFoodChange  = otGame.supplies.food - foodBefore;
        const eventFoodNet   = netFoodChange + travelFoodCost;  // positive = events gave food overall
    
        const summary = [
          `━━ ${daysOfTravel} days on the trail (${pace} pace) ━━`,
          `Travel cost: -${travelFoodCost} food, +${milesPerDay * daysOfTravel} miles`,
          eventFoodNet !== 0
            ? `Events: ${eventFoodNet > 0 ? "+" : ""}${eventFoodNet} food (net)`
            : `Events: no net food change`,
          `Food: ${foodBefore} → ${otGame.supplies.food}`,
        ].join("  |  ");
    
        const body = narratives.map((n, i) => `Day ${otGame!.day - (daysOfTravel - 1 - i)}: ${n}`).join("\n\n");
        return {
          content: [{
            type: "text",
            text: `${summary}\n\n${body}\n\n${renderState(otGame)}`,
          }],
        };
      }
    );
  • Helper function used by ot_travel to clamp health and supplies values between min/max bounds.
    function clamp(v: number, lo: number, hi: number): number {
      return Math.max(lo, Math.min(hi, v));
    }
  • Helper function used by ot_travel to roll random events during travel days. Returns pet-specific, normal, or mysterious events based on state.
    function rollEvent(state: OtState): GameEvent {
      const p   = state.player.name;
      const c   = state.companion.name;
      const pet = state.pet.name;
    
      // ~30% chance: draw from the species-specific pool when pet is alive
      if (state.pet.alive && Math.random() < 0.30) {
        const pool = PET_EVENTS.filter(ev => ev.petTypes!.includes(state.pet.type));
        if (pool.length > 0) return resolvePetEvent(pick(pool), state);
      }
    
      const normal: GameEvent[] = [
        // good weather
        { text: "Clear skies and a cool breeze. Spirits are high and the oxen move well.", playerHealth: 5, compHealth: 5 },
        { text: "A perfect traveling day. Everyone finds their stride.", playerHealth: 8, compHealth: 8, petHealth: 5 },
        // bad weather
        { text: `A violent storm rolls in without warning. You make camp soaked to the bone. ${p} wakes up shivering.`, playerHealth: -15, food: -1 },
        { text: `Scorching heat bakes the trail. The oxen slow to a crawl. ${c} barely speaks.`, compHealth: -12 },
        { text: "Three days of steady rain turn the trail to mud. Progress is miserable.", food: -2, playerHealth: -5, compHealth: -5 },
        // health bad
        { text: `${p} wakes up feverish. You press on, but it's slow going.`, playerHealth: -22 },
        { text: `${c} slips on loose rock and twists their ankle badly.`, compHealth: -25 },
        { text: `A mysterious cough spreads through camp. Both of you feel it by nightfall.`, playerHealth: -12, compHealth: -12 },
        { text: `Something in the water disagrees with ${p}. A rough night follows.`, playerHealth: -18 },
        // health good
        { text: "You find a hot spring just off the trail. Everyone soaks for an afternoon. Worth every minute.", playerHealth: 18, compHealth: 18, petHealth: 12 },
        { text: `${c} finds wild herbs that make a surprisingly restorative tea.`, playerHealth: 14, compHealth: 14 },
        // supplies found — abandoned camps (varied so each one feels like a different story)
        { text: "An abandoned camp at the roadside, cold for at least a day. No sign of why they left or where they went. The food is good, though.", food: 7, ammo: 10 },
        { text: "A wagon half-sunk in a ditch, stripped of everything except what no one wanted — some provisions and a crate of ammunition. You take both and don't linger.", food: 5, ammo: 20 },
        { text: `A campsite, recently abandoned — the fire is still warm. ${p} calls out. No answer comes back. You help yourselves to what's left and move on before dark.`, food: 9 },
        { text: "Someone left in a hurry. The bedrolls are still laid out. You salvage provisions and a spare part from the wagon wreck nearby, and decide not to think too hard about it.", food: 6, parts: 1 },
        { text: `An empty camp marked with a boot hung from a tree — an old trail signal meaning 'gone ahead, help yourself.' The provisions left behind suggest they were optimistic about what lay ahead.`, food: 10, ammo: 8 },
        { text: `A camp with the fire burned down to ash and a meal left half-eaten. ${c} checks the perimeter. Nothing. You pack the remaining food and leave quickly.`, food: 4, ammo: 12 },
        // supplies found — foraging and traders
        { text: "A good morning's foraging turns up berries and roots. Not glamorous, but it helps.", food: 5 },
        { text: `${c} spots something edible off the trail and spends an hour collecting it. Not a word of complaint. Just hands you a bundle of food and keeps walking.`, food: 7 },
        { text: "A passing trader offers a fair deal on surplus food.", food: 9 },
        { text: `A trader coming the other direction flags you down. They've got more food than they need and no interest in hauling it back. You negotiate quickly and part ways satisfied.`, food: 11, money: -5 },
        // supplies lost — wagon and oxen
        { text: "The wagon hits a deep rut. You hear a crack. A wheel spoke has splintered.", parts: -1 },
        { text: `A rocky descent rattles the wagon harder than expected. Something in the undercarriage shifts. ${c} inspects it and doesn't look happy.`, parts: -1 },
        { text: `The heat has been warping the wood. A board on the wagon bed splits clean through. You patch it as best you can, but it costs a part.`, parts: -1 },
        { text: "One of the oxen wanders off in the night. Hours of searching yield nothing.", oxen: -1 },
        { text: `One of the oxen has been favoring its left foreleg all day. By evening it won't bear weight. You lose time and burn provisions keeping the others moving.`, oxen: -1, food: -3 },
        // supplies lost — food and money
        { text: "Something got into the food supply overnight. Half a week's provisions are ruined.", food: -4 },
        { text: `Bandits step onto the trail. After a tense standoff they take what they came for and leave.`, money: -14, food: -2 },
        // pet health — general (applies regardless of species)
        { text: `${pet} seems off today — quieter than usual, slower, eating less. Nothing you can point to. You keep a close eye on them.`, petHealth: -14 },
        // encounters
        { text: `You pass a family heading the opposite direction. They look shaken. They won't say why.` },
        { text: `A wandering physician offers medicine at a fair price.`, medicine: 2, money: -10 },
        { text: `You share a fire with another group heading the same way. Food and stories are exchanged. A good night.`, food: 4, playerHealth: 8, compHealth: 8 },
      ];
    
      const mysterious: GameEvent[] = [
        { text: `You hear music on the wind with no visible source. It stops the moment ${c} tries to identify the melody.`, mysterious: true },
        { text: `Both of you describe the same dream at breakfast, word for word. Neither of you had mentioned it first.`, mysterious: true },
        { text: `A signpost appears on the trail — old wood, older than anything else you've seen. It points toward the Well.`, mysterious: true },
        { text: `An old woman at the roadside watches you pass. "The Well gives what you need," she says. "Not what you want." She won't say more.`, mysterious: true },
        { text: `The trail seems shorter today. You make better distance than you should have. ${c} checks the map twice.`, mysterious: true },
        { text: `${pet} stares into the fog for a long time, then looks at you, then back at the fog. You decide not to think about it.`, mysterious: true },
        { text: `You find a cairn of stones at the roadside, freshly stacked. No one else is on the trail. ${c} doesn't sleep well.`, mysterious: true },
        { text: `The fire goes out in the night and relights itself before either of you notices. ${c} is certain you both saw it happen. Neither of you mentions it again.`, mysterious: true },
        { text: `You find a page torn from a journal in the mud. The handwriting looks like ${p}'s. The date at the top is three weeks from now.`, mysterious: true },
        { text: `A child waves to you from the crest of a hill a quarter mile off the trail. When ${c} waves back, the child turns and walks into the ground like it was water.`, mysterious: true },
        { text: `Your shadow points the wrong direction for about an hour. Then it doesn't.`, mysterious: true },
        { text: `${c} stops speaking mid-sentence and stares past you for a full minute. When they come back, they say: "Sorry — did I say something?" They genuinely can't remember what they were saying.`, mysterious: true },
      ];
    
      const nearEnd = state.mile > 850;
      if (nearEnd && Math.random() < 0.55) return pick(mysterious);
      return pick(normal);
    }
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries full burden. It specifies costs per pace and mentions random events, but does not disclose failure conditions (e.g., insufficient food), whether travel is always successful, or the full range of random events.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Description is relatively compact with five sentences covering purpose, costs, and narrative instruction. However, some cost details are redundant with the schema, and the instruction to narrate could be integrated more smoothly.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool has no output schema, but description does not explain what the tool returns (e.g., narrative string, updated state). It also omits details like time progression, inventory checks, or failure handling, leaving gaps for an agent to infer.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and the parameter description repeats the same information as the tool description. The description adds no new semantic detail beyond the enum values and their costs, so baseline score of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

Description clearly states the tool is for traveling for 3 days with food and health costs. It distinguishes from other tools like ot_rest or ot_hunt by its specific action and resource changes, though it does not explicitly name siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides a hint to plan supplies before calling, implying the tool requires sufficient food. However, no explicit guidance on when to use travel versus alternatives like rest or hunt, nor conditions when not to use it.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/SrmTech-git/MCPArcade'

If you have feedback or need assistance with the MCP directory API, please join our Discord server