Spaces:
Running on Zero
Running on Zero
BhargavMN commited on
Commit ·
0acfbcd
1
Parent(s): 01da2ab
data set for fine tuning
Browse files- .gitignore +8 -0
- app/data/hide_and_seek/dataset.json +0 -0
- app/data/scavenger_hunt/dataset.json +0 -0
- app/data/tag/dataset.json +894 -0
- scripts/scavenger_hunt/generator.py +15 -138
- scripts/scavenger_hunt/sampler.py +25 -7
- scripts/scavenger_hunt/test_pipeline.py +36 -13
- scripts/scavenger_hunt/validator.py +111 -1
.gitignore
CHANGED
|
@@ -137,3 +137,11 @@ scripts/tag/run_generation.py
|
|
| 137 |
scripts/tag/test_pipeline.py
|
| 138 |
scripts/tag/validate_dataset.py
|
| 139 |
scripts/tag/validator.py
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
scripts/tag/test_pipeline.py
|
| 138 |
scripts/tag/validate_dataset.py
|
| 139 |
scripts/tag/validator.py
|
| 140 |
+
prompts/dataset_generation_context.md
|
| 141 |
+
scripts/common/batching.py
|
| 142 |
+
scripts/common/test_batching.py
|
| 143 |
+
scripts/scavenger_hunt/validator.py
|
| 144 |
+
scripts/scavenger_hunt/test_pipeline.py
|
| 145 |
+
scripts/scavenger_hunt/generator.py
|
| 146 |
+
scripts/common/cli.py
|
| 147 |
+
scripts/.batch_key_state.json
|
app/data/hide_and_seek/dataset.json
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
app/data/scavenger_hunt/dataset.json
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
app/data/tag/dataset.json
CHANGED
|
@@ -2789,5 +2789,899 @@
|
|
| 2789 |
"estimated_total_time_minutes": 45,
|
| 2790 |
"quality_score": 4.8
|
| 2791 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2792 |
}
|
| 2793 |
]
|
|
|
|
| 2789 |
"estimated_total_time_minutes": 45,
|
| 2790 |
"quality_score": 4.8
|
| 2791 |
}
|
| 2792 |
+
},
|
| 2793 |
+
{
|
| 2794 |
+
"id": "TG-LOS-0023",
|
| 2795 |
+
"input": {
|
| 2796 |
+
"game_type": "tag",
|
| 2797 |
+
"location": {
|
| 2798 |
+
"city": "Lagos",
|
| 2799 |
+
"country": "Nigeria",
|
| 2800 |
+
"city_code": "LOS",
|
| 2801 |
+
"landscape_tags": [
|
| 2802 |
+
"market_outdoor",
|
| 2803 |
+
"dense_urban_grid",
|
| 2804 |
+
"coastal_waterfront",
|
| 2805 |
+
"food_market"
|
| 2806 |
+
],
|
| 2807 |
+
"urban_density": "hyper_dense",
|
| 2808 |
+
"climate_zone": "tropical",
|
| 2809 |
+
"area_type": "waterfront"
|
| 2810 |
+
},
|
| 2811 |
+
"players": {
|
| 2812 |
+
"count": 10,
|
| 2813 |
+
"team_count": 1,
|
| 2814 |
+
"age_group": "mixed_family",
|
| 2815 |
+
"mobility": "standard"
|
| 2816 |
+
},
|
| 2817 |
+
"preferences": {
|
| 2818 |
+
"duration_minutes": 120,
|
| 2819 |
+
"difficulty": "easy",
|
| 2820 |
+
"theme": "urban_exploration",
|
| 2821 |
+
"allow_transport": false
|
| 2822 |
+
}
|
| 2823 |
+
},
|
| 2824 |
+
"output": {
|
| 2825 |
+
"game_title": "The Waterfront Market Chase",
|
| 2826 |
+
"rules": {
|
| 2827 |
+
"objective": "Avoid being tagged by 'It' players by reaching designated safe zones.",
|
| 2828 |
+
"tag_variant": "safe_zone_tag",
|
| 2829 |
+
"it_count": 2,
|
| 2830 |
+
"round_count": 6,
|
| 2831 |
+
"round_time_minutes": 20,
|
| 2832 |
+
"tag_mechanic": "A tag is made by an open-hand touch on any part of the body, accompanied by a clear verbal call-out of 'Tag!'. Grabbing, pushing, or pulling clothing is strictly prohibited.",
|
| 2833 |
+
"unfreeze_or_revival_rule": "Tagged players are out for the current round.",
|
| 2834 |
+
"win_condition": "Be the last player untagged when the round time expires."
|
| 2835 |
+
},
|
| 2836 |
+
"arena": {
|
| 2837 |
+
"boundary_description": "The play area is contained within the bustling outdoor market, bordered by the grid thoroughfares on two sides and the coastal waterfront promenade on another. The food market stalls delineate an inner boundary.",
|
| 2838 |
+
"boundary_landscape_tags_used": [
|
| 2839 |
+
"market_outdoor",
|
| 2840 |
+
"dense_urban_grid",
|
| 2841 |
+
"coastal_waterfront",
|
| 2842 |
+
"food_market"
|
| 2843 |
+
],
|
| 2844 |
+
"arena_size_tier": "medium",
|
| 2845 |
+
"out_of_bounds": [
|
| 2846 |
+
"Beyond the market boundaries",
|
| 2847 |
+
"into active roadways of the dense urban grid",
|
| 2848 |
+
"any entry into the coastal waterfront or its immediate banks"
|
| 2849 |
+
]
|
| 2850 |
+
},
|
| 2851 |
+
"safe_zones": [
|
| 2852 |
+
{
|
| 2853 |
+
"zone_id": "S01",
|
| 2854 |
+
"description": "The raised platform near the large market entrance.",
|
| 2855 |
+
"landscape_tags_used": [
|
| 2856 |
+
"market_outdoor"
|
| 2857 |
+
],
|
| 2858 |
+
"capacity_rule": "Max 3 players, 15-second limit.",
|
| 2859 |
+
"safety_flags": [
|
| 2860 |
+
"watch for tripping hazards"
|
| 2861 |
+
]
|
| 2862 |
+
},
|
| 2863 |
+
{
|
| 2864 |
+
"zone_id": "S02",
|
| 2865 |
+
"description": "The covered seating area within the food market.",
|
| 2866 |
+
"landscape_tags_used": [
|
| 2867 |
+
"food_market"
|
| 2868 |
+
],
|
| 2869 |
+
"capacity_rule": "Max 3 players, 15-second limit.",
|
| 2870 |
+
"safety_flags": [
|
| 2871 |
+
"ensure clear path to exit"
|
| 2872 |
+
]
|
| 2873 |
+
}
|
| 2874 |
+
],
|
| 2875 |
+
"movement_features": {
|
| 2876 |
+
"chokepoints": [
|
| 2877 |
+
"Narrow aisles between market stalls",
|
| 2878 |
+
"congested pathways within the food market",
|
| 2879 |
+
"pedestrian crossings at the edge of the dense urban grid"
|
| 2880 |
+
],
|
| 2881 |
+
"open_zones": [
|
| 2882 |
+
"The wide open area at the heart of the outdoor market",
|
| 2883 |
+
"the extended coastal waterfront promenade"
|
| 2884 |
+
],
|
| 2885 |
+
"hazard_zones": [
|
| 2886 |
+
"Coastal water edges, which may have slippery surfaces due to spray",
|
| 2887 |
+
"active vehicle roadways within the dense urban grid",
|
| 2888 |
+
"crowded market aisles with potential for bumping and tripping hazards from goods"
|
| 2889 |
+
]
|
| 2890 |
+
},
|
| 2891 |
+
"safety_constraints": {
|
| 2892 |
+
"exclusion_zones": [
|
| 2893 |
+
"private_property",
|
| 2894 |
+
"active_roadway",
|
| 2895 |
+
"construction_sites",
|
| 2896 |
+
"restricted_government_buildings",
|
| 2897 |
+
"water_edge"
|
| 2898 |
+
],
|
| 2899 |
+
"physical_limits": [
|
| 2900 |
+
"no grabbing or pushing — tag is an open-hand touch only",
|
| 2901 |
+
"no climbing",
|
| 2902 |
+
"no jumping",
|
| 2903 |
+
"no water entry",
|
| 2904 |
+
"no entering buildings",
|
| 2905 |
+
"no running across any roadway"
|
| 2906 |
+
],
|
| 2907 |
+
"collision_safety_note": "Players must be highly aware of other pedestrians and market stalls in this hyper-dense environment. Exercise caution for sudden stops, blind corners, and potential tripping hazards from goods on the ground in market aisles.",
|
| 2908 |
+
"adult_supervision_required": true,
|
| 2909 |
+
"notes": "Tropical climate: Players must stay well-hydrated and seek shade frequently. Adult supervisors should ensure shade access is available within 10 minutes of any point in the arena. Be aware of slippery surfaces near the waterfront."
|
| 2910 |
+
},
|
| 2911 |
+
"scoring": {
|
| 2912 |
+
"method": "survival_time",
|
| 2913 |
+
"winning_condition_detail": "The player who remains untagged for the longest cumulative time across all rounds wins the game."
|
| 2914 |
+
},
|
| 2915 |
+
"estimated_total_time_minutes": 120,
|
| 2916 |
+
"quality_score": 4.7
|
| 2917 |
+
}
|
| 2918 |
+
},
|
| 2919 |
+
{
|
| 2920 |
+
"id": "TG-BOM-0024",
|
| 2921 |
+
"input": {
|
| 2922 |
+
"game_type": "tag",
|
| 2923 |
+
"location": {
|
| 2924 |
+
"city": "Mumbai",
|
| 2925 |
+
"country": "India",
|
| 2926 |
+
"city_code": "BOM",
|
| 2927 |
+
"landscape_tags": [
|
| 2928 |
+
"market_outdoor",
|
| 2929 |
+
"religious_site_accessible",
|
| 2930 |
+
"dense_urban_grid",
|
| 2931 |
+
"coastal_waterfront",
|
| 2932 |
+
"food_market"
|
| 2933 |
+
],
|
| 2934 |
+
"urban_density": "hyper_dense",
|
| 2935 |
+
"climate_zone": "tropical",
|
| 2936 |
+
"area_type": "city_center"
|
| 2937 |
+
},
|
| 2938 |
+
"players": {
|
| 2939 |
+
"count": 16,
|
| 2940 |
+
"team_count": 1,
|
| 2941 |
+
"age_group": "teens",
|
| 2942 |
+
"mobility": "standard"
|
| 2943 |
+
},
|
| 2944 |
+
"preferences": {
|
| 2945 |
+
"duration_minutes": 60,
|
| 2946 |
+
"difficulty": "hard",
|
| 2947 |
+
"theme": "urban_exploration",
|
| 2948 |
+
"allow_transport": false
|
| 2949 |
+
}
|
| 2950 |
+
},
|
| 2951 |
+
"output": {
|
| 2952 |
+
"game_title": "Urban Grid Chain Chase",
|
| 2953 |
+
"rules": {
|
| 2954 |
+
"objective": "Avoid being tagged by the 'It' chain; tagged players join the chain to make it longer.",
|
| 2955 |
+
"tag_variant": "chain_tag",
|
| 2956 |
+
"it_count": 2,
|
| 2957 |
+
"round_count": 4,
|
| 2958 |
+
"round_time_minutes": 15,
|
| 2959 |
+
"tag_mechanic": "A tag is made by an open-hand touch on any part of the body, accompanied by a clear verbal call-out of 'Tag!'. Grabbing, pushing, or pulling clothing is strictly prohibited.",
|
| 2960 |
+
"unfreeze_or_revival_rule": "Tagged players immediately join the 'It' chain by holding hands with a player already in the chain. The chain must remain connected.",
|
| 2961 |
+
"win_condition": "The last player(s) not part of the 'It' chain when the round time expires wins the round."
|
| 2962 |
+
},
|
| 2963 |
+
"arena": {
|
| 2964 |
+
"boundary_description": "The expansive play area encompasses a dynamic outdoor market, a publicly accessible religious site, and extends through parts of the dense urban grid, reaching towards the coastal waterfront promenade. The food market sections are within the boundaries.",
|
| 2965 |
+
"boundary_landscape_tags_used": [
|
| 2966 |
+
"market_outdoor",
|
| 2967 |
+
"religious_site_accessible",
|
| 2968 |
+
"dense_urban_grid",
|
| 2969 |
+
"coastal_waterfront",
|
| 2970 |
+
"food_market"
|
| 2971 |
+
],
|
| 2972 |
+
"arena_size_tier": "wide",
|
| 2973 |
+
"out_of_bounds": [
|
| 2974 |
+
"Beyond the designated market perimeter",
|
| 2975 |
+
"crossing into active vehicle traffic lanes of the dense urban grid",
|
| 2976 |
+
"entering private property",
|
| 2977 |
+
"any entry into the coastal waterfront or its immediate banks",
|
| 2978 |
+
"inside religious building interiors"
|
| 2979 |
+
]
|
| 2980 |
+
},
|
| 2981 |
+
"safe_zones": [
|
| 2982 |
+
{
|
| 2983 |
+
"zone_id": "S01",
|
| 2984 |
+
"description": "The shaded courtyard of the accessible religious site.",
|
| 2985 |
+
"landscape_tags_used": [
|
| 2986 |
+
"religious_site_accessible"
|
| 2987 |
+
],
|
| 2988 |
+
"capacity_rule": "Max 4 players, 10-second limit.",
|
| 2989 |
+
"safety_flags": [
|
| 2990 |
+
"respect quiet zones"
|
| 2991 |
+
]
|
| 2992 |
+
},
|
| 2993 |
+
{
|
| 2994 |
+
"zone_id": "S02",
|
| 2995 |
+
"description": "A designated open space within the outdoor food market.",
|
| 2996 |
+
"landscape_tags_used": [
|
| 2997 |
+
"food_market",
|
| 2998 |
+
"market_outdoor"
|
| 2999 |
+
],
|
| 3000 |
+
"capacity_rule": "Max 4 players, 10-second limit.",
|
| 3001 |
+
"safety_flags": [
|
| 3002 |
+
"watch for uneven surfaces"
|
| 3003 |
+
]
|
| 3004 |
+
}
|
| 3005 |
+
],
|
| 3006 |
+
"movement_features": {
|
| 3007 |
+
"chokepoints": [
|
| 3008 |
+
"Narrow alleyways within the dense urban grid",
|
| 3009 |
+
"congested paths between outdoor market stalls",
|
| 3010 |
+
"archways leading into the religious site's courtyards"
|
| 3011 |
+
],
|
| 3012 |
+
"open_zones": [
|
| 3013 |
+
"the square area of the outdoor market",
|
| 3014 |
+
"the open plaza near the religious site entrance",
|
| 3015 |
+
"sections of the coastal waterfront promenade"
|
| 3016 |
+
],
|
| 3017 |
+
"hazard_zones": [
|
| 3018 |
+
"Coastal water edges, which can be slippery due to spray or algae",
|
| 3019 |
+
"active roadways with vehicle traffic within the dense urban grid",
|
| 3020 |
+
"crowded market areas with potential for collisions and uneven ground",
|
| 3021 |
+
"steep steps or ramps within the religious site area"
|
| 3022 |
+
]
|
| 3023 |
+
},
|
| 3024 |
+
"safety_constraints": {
|
| 3025 |
+
"exclusion_zones": [
|
| 3026 |
+
"private_property",
|
| 3027 |
+
"active_roadway",
|
| 3028 |
+
"construction_sites",
|
| 3029 |
+
"restricted_government_buildings",
|
| 3030 |
+
"water_edge",
|
| 3031 |
+
"religious_interiors"
|
| 3032 |
+
],
|
| 3033 |
+
"physical_limits": [
|
| 3034 |
+
"no grabbing or pushing — tag is an open-hand touch only",
|
| 3035 |
+
"no climbing",
|
| 3036 |
+
"no jumping",
|
| 3037 |
+
"no water entry",
|
| 3038 |
+
"no entering buildings",
|
| 3039 |
+
"no running across any roadway"
|
| 3040 |
+
],
|
| 3041 |
+
"collision_safety_note": "Players must be extremely vigilant for other pedestrians, moving market carts, and uneven ground in the hyper-dense market and urban grid. Maintain awareness of blind corners in alleyways and avoid running near the steep drops at the waterfront edge.",
|
| 3042 |
+
"adult_supervision_required": false,
|
| 3043 |
+
"notes": "Tropical climate: Players must stay well-hydrated and seek shade frequently. Require shade access to be available within 10 minutes of any point in the arena. Be aware of slippery surfaces near the waterfront and potential crowded conditions."
|
| 3044 |
+
},
|
| 3045 |
+
"scoring": {
|
| 3046 |
+
"method": "last_untagged_wins",
|
| 3047 |
+
"winning_condition_detail": "The player who is the last one not part of the 'It' chain at the end of the final round wins the game."
|
| 3048 |
+
},
|
| 3049 |
+
"estimated_total_time_minutes": 60,
|
| 3050 |
+
"quality_score": 4.6
|
| 3051 |
+
}
|
| 3052 |
+
},
|
| 3053 |
+
{
|
| 3054 |
+
"id": "TG-BOM-0025",
|
| 3055 |
+
"input": {
|
| 3056 |
+
"game_type": "tag",
|
| 3057 |
+
"location": {
|
| 3058 |
+
"city": "Mumbai",
|
| 3059 |
+
"country": "India",
|
| 3060 |
+
"city_code": "BOM",
|
| 3061 |
+
"landscape_tags": [
|
| 3062 |
+
"religious_site_accessible",
|
| 3063 |
+
"dense_urban_grid",
|
| 3064 |
+
"coastal_waterfront"
|
| 3065 |
+
],
|
| 3066 |
+
"urban_density": "hyper_dense",
|
| 3067 |
+
"climate_zone": "tropical",
|
| 3068 |
+
"area_type": "waterfront"
|
| 3069 |
+
},
|
| 3070 |
+
"players": {
|
| 3071 |
+
"count": 14,
|
| 3072 |
+
"team_count": 1,
|
| 3073 |
+
"age_group": "mixed_family",
|
| 3074 |
+
"mobility": "standard"
|
| 3075 |
+
},
|
| 3076 |
+
"preferences": {
|
| 3077 |
+
"duration_minutes": 90,
|
| 3078 |
+
"difficulty": "medium",
|
| 3079 |
+
"theme": "nature",
|
| 3080 |
+
"allow_transport": false
|
| 3081 |
+
}
|
| 3082 |
+
},
|
| 3083 |
+
"output": {
|
| 3084 |
+
"game_title": "Waterfront Freeze Pursuit",
|
| 3085 |
+
"rules": {
|
| 3086 |
+
"objective": "Avoid being tagged by 'It' players; unfreeze teammates to keep the game going.",
|
| 3087 |
+
"tag_variant": "freeze_tag",
|
| 3088 |
+
"it_count": 2,
|
| 3089 |
+
"round_count": 5,
|
| 3090 |
+
"round_time_minutes": 18,
|
| 3091 |
+
"tag_mechanic": "A tag is made by an open-hand touch on any part of the body, accompanied by a clear verbal call-out of 'Tag!'. Grabbing, pushing, or pulling clothing is strictly prohibited.",
|
| 3092 |
+
"unfreeze_or_revival_rule": "Frozen players must stand still until unfrozen by a teammate's open-hand touch.",
|
| 3093 |
+
"win_condition": "Be the last player unfrozen when the round time expires."
|
| 3094 |
+
},
|
| 3095 |
+
"arena": {
|
| 3096 |
+
"boundary_description": "The play area spans a section of the dense urban grid, including a publicly accessible religious site and extending along the coastal waterfront promenade.",
|
| 3097 |
+
"boundary_landscape_tags_used": [
|
| 3098 |
+
"religious_site_accessible",
|
| 3099 |
+
"dense_urban_grid",
|
| 3100 |
+
"coastal_waterfront"
|
| 3101 |
+
],
|
| 3102 |
+
"arena_size_tier": "medium",
|
| 3103 |
+
"out_of_bounds": [
|
| 3104 |
+
"Beyond the urban grid's pedestrian zones",
|
| 3105 |
+
"into active vehicle roadways",
|
| 3106 |
+
"any entry into the coastal waterfront or its immediate banks",
|
| 3107 |
+
"inside religious building interiors"
|
| 3108 |
+
]
|
| 3109 |
+
},
|
| 3110 |
+
"safe_zones": [
|
| 3111 |
+
{
|
| 3112 |
+
"zone_id": "S01",
|
| 3113 |
+
"description": "The shaded veranda within the accessible religious site.",
|
| 3114 |
+
"landscape_tags_used": [
|
| 3115 |
+
"religious_site_accessible"
|
| 3116 |
+
],
|
| 3117 |
+
"capacity_rule": "Max 3 players, 10-second limit.",
|
| 3118 |
+
"safety_flags": [
|
| 3119 |
+
"respect quiet zones"
|
| 3120 |
+
]
|
| 3121 |
+
},
|
| 3122 |
+
{
|
| 3123 |
+
"zone_id": "S02",
|
| 3124 |
+
"description": "A designated seating area on the waterfront promenade.",
|
| 3125 |
+
"landscape_tags_used": [
|
| 3126 |
+
"coastal_waterfront"
|
| 3127 |
+
],
|
| 3128 |
+
"capacity_rule": "Max 3 players, 10-second limit.",
|
| 3129 |
+
"safety_flags": [
|
| 3130 |
+
"watch for slippery surfaces near water"
|
| 3131 |
+
]
|
| 3132 |
+
}
|
| 3133 |
+
],
|
| 3134 |
+
"movement_features": {
|
| 3135 |
+
"chokepoints": [
|
| 3136 |
+
"Narrow passages between buildings in the dense urban grid",
|
| 3137 |
+
"archways leading into the religious site",
|
| 3138 |
+
"gaps between vendors along the waterfront"
|
| 3139 |
+
],
|
| 3140 |
+
"open_zones": [
|
| 3141 |
+
"The open plaza area of the religious site",
|
| 3142 |
+
"the wide sections of the coastal waterfront promenade"
|
| 3143 |
+
],
|
| 3144 |
+
"hazard_zones": [
|
| 3145 |
+
"Coastal water edges, which can be slippery due to spray or algae",
|
| 3146 |
+
"active roadways with fast-moving traffic within the dense urban grid",
|
| 3147 |
+
"uneven stone pathways and steps within the religious site"
|
| 3148 |
+
]
|
| 3149 |
+
},
|
| 3150 |
+
"safety_constraints": {
|
| 3151 |
+
"exclusion_zones": [
|
| 3152 |
+
"private_property",
|
| 3153 |
+
"active_roadway",
|
| 3154 |
+
"construction_sites",
|
| 3155 |
+
"restricted_government_buildings",
|
| 3156 |
+
"water_edge",
|
| 3157 |
+
"religious_interiors"
|
| 3158 |
+
],
|
| 3159 |
+
"physical_limits": [
|
| 3160 |
+
"no grabbing or pushing — tag is an open-hand touch only",
|
| 3161 |
+
"no climbing",
|
| 3162 |
+
"no jumping",
|
| 3163 |
+
"no water entry",
|
| 3164 |
+
"no entering buildings",
|
| 3165 |
+
"no running across any roadway"
|
| 3166 |
+
],
|
| 3167 |
+
"collision_safety_note": "Players must maintain constant awareness of other pedestrians and obstacles in the dense urban grid and along the waterfront. Be cautious of blind corners and sudden changes in elevation near the religious site, and slippery surfaces near the water's edge.",
|
| 3168 |
+
"adult_supervision_required": true,
|
| 3169 |
+
"notes": "Tropical climate: Players must stay well-hydrated and seek shade frequently. Adult supervisors should ensure shade access is available within 10 minutes of any point in the arena. Be aware of slippery surfaces near the waterfront."
|
| 3170 |
+
},
|
| 3171 |
+
"scoring": {
|
| 3172 |
+
"method": "survival_time",
|
| 3173 |
+
"winning_condition_detail": "The player who remains unfrozen for the longest cumulative time across all rounds wins the game."
|
| 3174 |
+
},
|
| 3175 |
+
"estimated_total_time_minutes": 90,
|
| 3176 |
+
"quality_score": 4.6
|
| 3177 |
+
}
|
| 3178 |
+
},
|
| 3179 |
+
{
|
| 3180 |
+
"id": "TG-IST-0026",
|
| 3181 |
+
"input": {
|
| 3182 |
+
"game_type": "tag",
|
| 3183 |
+
"location": {
|
| 3184 |
+
"city": "Istanbul",
|
| 3185 |
+
"country": "Turkey",
|
| 3186 |
+
"city_code": "IST",
|
| 3187 |
+
"landscape_tags": [
|
| 3188 |
+
"plaza_or_square",
|
| 3189 |
+
"historic_district",
|
| 3190 |
+
"market_covered",
|
| 3191 |
+
"hill_or_elevation"
|
| 3192 |
+
],
|
| 3193 |
+
"urban_density": "dense",
|
| 3194 |
+
"climate_zone": "mediterranean",
|
| 3195 |
+
"area_type": "city_center"
|
| 3196 |
+
},
|
| 3197 |
+
"players": {
|
| 3198 |
+
"count": 10,
|
| 3199 |
+
"team_count": 1,
|
| 3200 |
+
"age_group": "adults",
|
| 3201 |
+
"mobility": "limited"
|
| 3202 |
+
},
|
| 3203 |
+
"preferences": {
|
| 3204 |
+
"duration_minutes": 120,
|
| 3205 |
+
"difficulty": "medium",
|
| 3206 |
+
"theme": "urban_exploration",
|
| 3207 |
+
"allow_transport": false
|
| 3208 |
+
}
|
| 3209 |
+
},
|
| 3210 |
+
"output": {
|
| 3211 |
+
"game_title": "Historic District Freeze Out",
|
| 3212 |
+
"rules": {
|
| 3213 |
+
"objective": "Avoid being tagged by 'It' players and unfreeze your teammates.",
|
| 3214 |
+
"tag_variant": "freeze_tag",
|
| 3215 |
+
"it_count": 2,
|
| 3216 |
+
"round_count": 6,
|
| 3217 |
+
"round_time_minutes": 20,
|
| 3218 |
+
"tag_mechanic": "A tag is made by an open-hand touch on any part of the body, accompanied by a clear verbal call-out of 'Tag!'. Grabbing, pushing, or pulling clothing is strictly prohibited.",
|
| 3219 |
+
"unfreeze_or_revival_rule": "Frozen players must stand still until unfrozen by a teammate's open-hand touch.",
|
| 3220 |
+
"win_condition": "Be the last player unfrozen when the round time expires."
|
| 3221 |
+
},
|
| 3222 |
+
"arena": {
|
| 3223 |
+
"boundary_description": "The play area is defined by the pedestrian zones of a historic district, encompassing a grand plaza, a bustling covered market, and the base of a significant elevation.",
|
| 3224 |
+
"boundary_landscape_tags_used": [
|
| 3225 |
+
"plaza_or_square",
|
| 3226 |
+
"historic_district",
|
| 3227 |
+
"market_covered",
|
| 3228 |
+
"hill_or_elevation"
|
| 3229 |
+
],
|
| 3230 |
+
"arena_size_tier": "medium",
|
| 3231 |
+
"out_of_bounds": [
|
| 3232 |
+
"Beyond the designated pedestrian pathways",
|
| 3233 |
+
"into active vehicle roadways",
|
| 3234 |
+
"inside private establishments within the covered market"
|
| 3235 |
+
]
|
| 3236 |
+
},
|
| 3237 |
+
"safe_zones": [
|
| 3238 |
+
{
|
| 3239 |
+
"zone_id": "S01",
|
| 3240 |
+
"description": "the fountain area within the large plaza.",
|
| 3241 |
+
"landscape_tags_used": [
|
| 3242 |
+
"plaza_or_square"
|
| 3243 |
+
],
|
| 3244 |
+
"capacity_rule": "Max 2 players, 10-second limit.",
|
| 3245 |
+
"safety_flags": [
|
| 3246 |
+
"watch for uneven paving"
|
| 3247 |
+
]
|
| 3248 |
+
},
|
| 3249 |
+
{
|
| 3250 |
+
"zone_id": "S02",
|
| 3251 |
+
"description": "A designated waiting area inside the covered market.",
|
| 3252 |
+
"landscape_tags_used": [
|
| 3253 |
+
"market_covered"
|
| 3254 |
+
],
|
| 3255 |
+
"capacity_rule": "Max 2 players, 10-second limit.",
|
| 3256 |
+
"safety_flags": [
|
| 3257 |
+
"mind your step on inclines"
|
| 3258 |
+
]
|
| 3259 |
+
}
|
| 3260 |
+
],
|
| 3261 |
+
"movement_features": {
|
| 3262 |
+
"chokepoints": [
|
| 3263 |
+
"Narrow alleys within the historic district",
|
| 3264 |
+
"entrances and exits of the covered market",
|
| 3265 |
+
"steep, winding passages leading up the hill or elevation"
|
| 3266 |
+
],
|
| 3267 |
+
"open_zones": [
|
| 3268 |
+
"The expansive open space of the plaza or square",
|
| 3269 |
+
"the wide corridors within the covered market"
|
| 3270 |
+
],
|
| 3271 |
+
"hazard_zones": [
|
| 3272 |
+
"Steep inclines and declines with potential for slips",
|
| 3273 |
+
"uneven cobblestone surfaces in the historic district",
|
| 3274 |
+
"crowded passages within the covered market with low visibility"
|
| 3275 |
+
]
|
| 3276 |
+
},
|
| 3277 |
+
"safety_constraints": {
|
| 3278 |
+
"exclusion_zones": [
|
| 3279 |
+
"private_property",
|
| 3280 |
+
"active_roadway",
|
| 3281 |
+
"construction_sites",
|
| 3282 |
+
"restricted_government_buildings"
|
| 3283 |
+
],
|
| 3284 |
+
"physical_limits": [
|
| 3285 |
+
"no grabbing or pushing — tag is an open-hand touch only",
|
| 3286 |
+
"no climbing",
|
| 3287 |
+
"no jumping",
|
| 3288 |
+
"no water entry",
|
| 3289 |
+
"no entering buildings",
|
| 3290 |
+
"no running across any roadway"
|
| 3291 |
+
],
|
| 3292 |
+
"collision_safety_note": "Players must be aware of other pedestrians and market stalls. Exercise caution on uneven historic paving, especially on slopes, and navigate crowded covered market areas carefully, watching for blind corners and low-hanging objects.",
|
| 3293 |
+
"adult_supervision_required": false,
|
| 3294 |
+
"notes": "Mediterranean climate: no special constraints regarding temperature; however, players should stay hydrated. Be mindful of uneven surfaces and potential slips on inclines and declines."
|
| 3295 |
+
},
|
| 3296 |
+
"scoring": {
|
| 3297 |
+
"method": "survival_time",
|
| 3298 |
+
"winning_condition_detail": "The player who remains unfrozen for the longest cumulative time across all rounds wins the game."
|
| 3299 |
+
},
|
| 3300 |
+
"estimated_total_time_minutes": 120,
|
| 3301 |
+
"quality_score": 4.4
|
| 3302 |
+
}
|
| 3303 |
+
},
|
| 3304 |
+
{
|
| 3305 |
+
"id": "TG-CPT-0027",
|
| 3306 |
+
"input": {
|
| 3307 |
+
"game_type": "tag",
|
| 3308 |
+
"location": {
|
| 3309 |
+
"city": "Cape Town",
|
| 3310 |
+
"country": "South Africa",
|
| 3311 |
+
"city_code": "CPT",
|
| 3312 |
+
"landscape_tags": [
|
| 3313 |
+
"historic_district",
|
| 3314 |
+
"market_outdoor",
|
| 3315 |
+
"hill_or_elevation",
|
| 3316 |
+
"coastal_waterfront",
|
| 3317 |
+
"garden_formal"
|
| 3318 |
+
],
|
| 3319 |
+
"urban_density": "mixed",
|
| 3320 |
+
"climate_zone": "mediterranean",
|
| 3321 |
+
"area_type": "mixed_residential"
|
| 3322 |
+
},
|
| 3323 |
+
"players": {
|
| 3324 |
+
"count": 5,
|
| 3325 |
+
"team_count": 2,
|
| 3326 |
+
"age_group": "mixed_family",
|
| 3327 |
+
"mobility": "standard"
|
| 3328 |
+
},
|
| 3329 |
+
"preferences": {
|
| 3330 |
+
"duration_minutes": 90,
|
| 3331 |
+
"difficulty": "medium",
|
| 3332 |
+
"theme": "social",
|
| 3333 |
+
"allow_transport": true
|
| 3334 |
+
}
|
| 3335 |
+
},
|
| 3336 |
+
"output": {
|
| 3337 |
+
"game_title": "Coastal Garden Freeze",
|
| 3338 |
+
"rules": {
|
| 3339 |
+
"objective": "As a team, avoid being tagged by the 'It' player and work together to unfreeze teammates.",
|
| 3340 |
+
"tag_variant": "freeze_tag",
|
| 3341 |
+
"it_count": 1,
|
| 3342 |
+
"round_count": 5,
|
| 3343 |
+
"round_time_minutes": 18,
|
| 3344 |
+
"tag_mechanic": "A tag is made by an open-hand touch on any part of the body, accompanied by a clear verbal call-out of 'Tag!'. Grabbing, pushing, or pulling clothing is strictly prohibited.",
|
| 3345 |
+
"unfreeze_or_revival_rule": "Frozen players must stand still until unfrozen by a teammate's open-hand touch.",
|
| 3346 |
+
"win_condition": "The team with the most unfrozen players at the end of the round or the last team with players remaining wins the round."
|
| 3347 |
+
},
|
| 3348 |
+
"arena": {
|
| 3349 |
+
"boundary_description": "The wide play area encompasses a historic district, a sprawling outdoor market, a formal garden, and extends to the coastal waterfront, including paths that traverse minor hills or elevations.",
|
| 3350 |
+
"boundary_landscape_tags_used": [
|
| 3351 |
+
"historic_district",
|
| 3352 |
+
"market_outdoor",
|
| 3353 |
+
"hill_or_elevation",
|
| 3354 |
+
"coastal_waterfront",
|
| 3355 |
+
"garden_formal"
|
| 3356 |
+
],
|
| 3357 |
+
"arena_size_tier": "wide",
|
| 3358 |
+
"out_of_bounds": [
|
| 3359 |
+
"Beyond the formal garden fences",
|
| 3360 |
+
"into active vehicle roadways",
|
| 3361 |
+
"into private residential areas",
|
| 3362 |
+
"any entry into the coastal waterfront or its immediate banks"
|
| 3363 |
+
]
|
| 3364 |
+
},
|
| 3365 |
+
"safe_zones": [
|
| 3366 |
+
{
|
| 3367 |
+
"zone_id": "S01",
|
| 3368 |
+
"description": "The gazebo structure within the formal garden.",
|
| 3369 |
+
"landscape_tags_used": [
|
| 3370 |
+
"garden_formal"
|
| 3371 |
+
],
|
| 3372 |
+
"capacity_rule": "Max 2 players, 10-second limit.",
|
| 3373 |
+
"safety_flags": [
|
| 3374 |
+
"watch for slippery steps"
|
| 3375 |
+
]
|
| 3376 |
+
},
|
| 3377 |
+
{
|
| 3378 |
+
"zone_id": "S02",
|
| 3379 |
+
"description": "A designated shaded area within the outdoor market.",
|
| 3380 |
+
"landscape_tags_used": [
|
| 3381 |
+
"market_outdoor"
|
| 3382 |
+
],
|
| 3383 |
+
"capacity_rule": "Max 2 players, 10-second limit.",
|
| 3384 |
+
"safety_flags": [
|
| 3385 |
+
"clear path to exit"
|
| 3386 |
+
]
|
| 3387 |
+
}
|
| 3388 |
+
],
|
| 3389 |
+
"movement_features": {
|
| 3390 |
+
"chokepoints": [
|
| 3391 |
+
"Narrow pedestrian lanes in the historic district",
|
| 3392 |
+
"paths winding through the market stalls",
|
| 3393 |
+
"steep, winding paths on the hill or elevation",
|
| 3394 |
+
"archways leading into the formal garden"
|
| 3395 |
+
],
|
| 3396 |
+
"open_zones": [
|
| 3397 |
+
"The wide open plaza within the historic district",
|
| 3398 |
+
"the square of the outdoor market",
|
| 3399 |
+
"the expansive lawns of the formal garden",
|
| 3400 |
+
"the coastal waterfront promenade"
|
| 3401 |
+
],
|
| 3402 |
+
"hazard_zones": [
|
| 3403 |
+
"Coastal water edges, which can be slippery or have sudden drops",
|
| 3404 |
+
"steep, uneven slopes and steps on the hills",
|
| 3405 |
+
"crowded market pathways with potential for collisions and tripping hazards from goods"
|
| 3406 |
+
]
|
| 3407 |
+
},
|
| 3408 |
+
"safety_constraints": {
|
| 3409 |
+
"exclusion_zones": [
|
| 3410 |
+
"private_property",
|
| 3411 |
+
"active_roadway",
|
| 3412 |
+
"construction_sites",
|
| 3413 |
+
"restricted_government_buildings",
|
| 3414 |
+
"water_edge"
|
| 3415 |
+
],
|
| 3416 |
+
"physical_limits": [
|
| 3417 |
+
"no grabbing or pushing — tag is an open-hand touch only",
|
| 3418 |
+
"no climbing",
|
| 3419 |
+
"no jumping",
|
| 3420 |
+
"no water entry",
|
| 3421 |
+
"no entering buildings",
|
| 3422 |
+
"no running across any roadway"
|
| 3423 |
+
],
|
| 3424 |
+
"collision_safety_note": "Players should be cautious of uneven ground and steep slopes, especially when running downhill. In crowded market areas, be mindful of other players and market structures. Watch for slippery surfaces near the waterfront.",
|
| 3425 |
+
"adult_supervision_required": true,
|
| 3426 |
+
"notes": "Mediterranean climate: no special constraints regarding temperature; however, players should stay hydrated. Be mindful of uneven surfaces on hills and near the waterfront."
|
| 3427 |
+
},
|
| 3428 |
+
"scoring": {
|
| 3429 |
+
"method": "survival_time",
|
| 3430 |
+
"winning_condition_detail": "The team with the highest cumulative unfrozen player time across all rounds wins the game."
|
| 3431 |
+
},
|
| 3432 |
+
"estimated_total_time_minutes": 90,
|
| 3433 |
+
"quality_score": 4.5
|
| 3434 |
+
}
|
| 3435 |
+
},
|
| 3436 |
+
{
|
| 3437 |
+
"id": "TG-CPT-0028",
|
| 3438 |
+
"input": {
|
| 3439 |
+
"game_type": "tag",
|
| 3440 |
+
"location": {
|
| 3441 |
+
"city": "Cape Town",
|
| 3442 |
+
"country": "South Africa",
|
| 3443 |
+
"city_code": "CPT",
|
| 3444 |
+
"landscape_tags": [
|
| 3445 |
+
"hill_or_elevation",
|
| 3446 |
+
"market_outdoor",
|
| 3447 |
+
"coastal_waterfront"
|
| 3448 |
+
],
|
| 3449 |
+
"urban_density": "mixed",
|
| 3450 |
+
"climate_zone": "mediterranean",
|
| 3451 |
+
"area_type": "waterfront"
|
| 3452 |
+
},
|
| 3453 |
+
"players": {
|
| 3454 |
+
"count": 20,
|
| 3455 |
+
"team_count": 1,
|
| 3456 |
+
"age_group": "mixed_family",
|
| 3457 |
+
"mobility": "limited"
|
| 3458 |
+
},
|
| 3459 |
+
"preferences": {
|
| 3460 |
+
"duration_minutes": 90,
|
| 3461 |
+
"difficulty": "hard",
|
| 3462 |
+
"theme": "urban_exploration",
|
| 3463 |
+
"allow_transport": false
|
| 3464 |
+
}
|
| 3465 |
+
},
|
| 3466 |
+
"output": {
|
| 3467 |
+
"game_title": "Waterfront Hilltop Freeze",
|
| 3468 |
+
"rules": {
|
| 3469 |
+
"objective": "Avoid being tagged by the 'It' players; unfreeze teammates to stay in the game.",
|
| 3470 |
+
"tag_variant": "freeze_tag",
|
| 3471 |
+
"it_count": 3,
|
| 3472 |
+
"round_count": 5,
|
| 3473 |
+
"round_time_minutes": 18,
|
| 3474 |
+
"tag_mechanic": "A tag is made by an open-hand touch on any part of the body, accompanied by a clear verbal call-out of 'Tag!'. Grabbing, pushing, or pulling clothing is strictly prohibited.",
|
| 3475 |
+
"unfreeze_or_revival_rule": "Frozen players must stand still until unfrozen by a teammate's open-hand touch.",
|
| 3476 |
+
"win_condition": "Be the last player unfrozen when the round time expires."
|
| 3477 |
+
},
|
| 3478 |
+
"arena": {
|
| 3479 |
+
"boundary_description": "The wide play area encompasses a large outdoor market, paths traversing significant hills or elevations, and the extensive coastal waterfront promenade.",
|
| 3480 |
+
"boundary_landscape_tags_used": [
|
| 3481 |
+
"hill_or_elevation",
|
| 3482 |
+
"market_outdoor",
|
| 3483 |
+
"coastal_waterfront"
|
| 3484 |
+
],
|
| 3485 |
+
"arena_size_tier": "wide",
|
| 3486 |
+
"out_of_bounds": [
|
| 3487 |
+
"Beyond the market boundaries",
|
| 3488 |
+
"into active vehicle roadways",
|
| 3489 |
+
"private property",
|
| 3490 |
+
"any entry into the coastal waterfront or its immediate banks"
|
| 3491 |
+
]
|
| 3492 |
+
},
|
| 3493 |
+
"safe_zones": [
|
| 3494 |
+
{
|
| 3495 |
+
"zone_id": "S01",
|
| 3496 |
+
"description": "A sheltered alcove within the outdoor market.",
|
| 3497 |
+
"landscape_tags_used": [
|
| 3498 |
+
"market_outdoor"
|
| 3499 |
+
],
|
| 3500 |
+
"capacity_rule": "Max 4 players, 10-second limit.",
|
| 3501 |
+
"safety_flags": [
|
| 3502 |
+
"watch for uneven ground"
|
| 3503 |
+
]
|
| 3504 |
+
},
|
| 3505 |
+
{
|
| 3506 |
+
"zone_id": "S02",
|
| 3507 |
+
"description": "The flat, paved lookout point on an elevated section.",
|
| 3508 |
+
"landscape_tags_used": [
|
| 3509 |
+
"hill_or_elevation"
|
| 3510 |
+
],
|
| 3511 |
+
"capacity_rule": "Max 4 players, 10-second limit.",
|
| 3512 |
+
"safety_flags": [
|
| 3513 |
+
"avoid slippery slopes"
|
| 3514 |
+
]
|
| 3515 |
+
}
|
| 3516 |
+
],
|
| 3517 |
+
"movement_features": {
|
| 3518 |
+
"chokepoints": [
|
| 3519 |
+
"Narrow staircases and winding paths on the hills or elevations",
|
| 3520 |
+
"congested pathways between market stalls",
|
| 3521 |
+
"pedestrian bridges crossing small inlets along the waterfront"
|
| 3522 |
+
],
|
| 3523 |
+
"open_zones": [
|
| 3524 |
+
"The expansive open area of the outdoor market square",
|
| 3525 |
+
"the wide coastal waterfront promenade",
|
| 3526 |
+
"flat plateau areas on the hills or elevations"
|
| 3527 |
+
],
|
| 3528 |
+
"hazard_zones": [
|
| 3529 |
+
"Coastal water edges, which can be slippery with sudden drops",
|
| 3530 |
+
"steep, uneven slopes and steps with loose gravel",
|
| 3531 |
+
"blind corners on winding paths",
|
| 3532 |
+
"crowded market aisles with potential for collisions and hidden obstacles"
|
| 3533 |
+
]
|
| 3534 |
+
},
|
| 3535 |
+
"safety_constraints": {
|
| 3536 |
+
"exclusion_zones": [
|
| 3537 |
+
"private_property",
|
| 3538 |
+
"active_roadway",
|
| 3539 |
+
"construction_sites",
|
| 3540 |
+
"restricted_government_buildings",
|
| 3541 |
+
"water_edge"
|
| 3542 |
+
],
|
| 3543 |
+
"physical_limits": [
|
| 3544 |
+
"no grabbing or pushing — tag is an open-hand touch only",
|
| 3545 |
+
"no climbing",
|
| 3546 |
+
"no jumping",
|
| 3547 |
+
"no water entry",
|
| 3548 |
+
"no entering buildings",
|
| 3549 |
+
"no running across any roadway"
|
| 3550 |
+
],
|
| 3551 |
+
"collision_safety_note": "Players with limited mobility should exercise extreme caution on steep and uneven terrain, watching for loose footing and blind corners. In crowded market areas, maintain awareness of others to avoid collisions. Be mindful of slippery surfaces and sudden drops near the waterfront.",
|
| 3552 |
+
"adult_supervision_required": true,
|
| 3553 |
+
"notes": "Mediterranean climate: no special constraints regarding temperature; however, players should stay hydrated. Be highly aware of uneven surfaces, steep slopes, and slippery areas near the waterfront due to the 'limited' mobility group and 'hard' difficulty."
|
| 3554 |
+
},
|
| 3555 |
+
"scoring": {
|
| 3556 |
+
"method": "survival_time",
|
| 3557 |
+
"winning_condition_detail": "The player who remains unfrozen for the longest cumulative time across all rounds wins the game."
|
| 3558 |
+
},
|
| 3559 |
+
"estimated_total_time_minutes": 90,
|
| 3560 |
+
"quality_score": 4.7
|
| 3561 |
+
}
|
| 3562 |
+
},
|
| 3563 |
+
{
|
| 3564 |
+
"id": "TG-BOG-0029",
|
| 3565 |
+
"input": {
|
| 3566 |
+
"game_type": "tag",
|
| 3567 |
+
"location": {
|
| 3568 |
+
"city": "Bogota",
|
| 3569 |
+
"country": "Colombia",
|
| 3570 |
+
"city_code": "BOG",
|
| 3571 |
+
"landscape_tags": [
|
| 3572 |
+
"street_art_district",
|
| 3573 |
+
"historic_district",
|
| 3574 |
+
"plaza_or_square"
|
| 3575 |
+
],
|
| 3576 |
+
"urban_density": "dense",
|
| 3577 |
+
"climate_zone": "temperate",
|
| 3578 |
+
"area_type": "waterfront"
|
| 3579 |
+
},
|
| 3580 |
+
"players": {
|
| 3581 |
+
"count": 4,
|
| 3582 |
+
"team_count": 5,
|
| 3583 |
+
"age_group": "mixed_family",
|
| 3584 |
+
"mobility": "standard"
|
| 3585 |
+
},
|
| 3586 |
+
"preferences": {
|
| 3587 |
+
"duration_minutes": 60,
|
| 3588 |
+
"difficulty": "hard",
|
| 3589 |
+
"theme": "logic",
|
| 3590 |
+
"allow_transport": false
|
| 3591 |
+
}
|
| 3592 |
+
},
|
| 3593 |
+
"output": {
|
| 3594 |
+
"game_title": "Historic Plaza Freeze",
|
| 3595 |
+
"rules": {
|
| 3596 |
+
"objective": "Avoid being tagged by the 'It' players; unfreeze teammates to stay in the game.",
|
| 3597 |
+
"tag_variant": "freeze_tag",
|
| 3598 |
+
"it_count": 2,
|
| 3599 |
+
"round_count": 4,
|
| 3600 |
+
"round_time_minutes": 15,
|
| 3601 |
+
"tag_mechanic": "A tag is made by an open-hand touch on any part of the body, accompanied by a clear verbal call-out of 'Tag!'. Grabbing, pushing, or pulling clothing is strictly prohibited.",
|
| 3602 |
+
"unfreeze_or_revival_rule": "Frozen players must stand still until unfrozen by a teammate's open-hand touch.",
|
| 3603 |
+
"win_condition": "Be the last player unfrozen when the round time expires."
|
| 3604 |
+
},
|
| 3605 |
+
"arena": {
|
| 3606 |
+
"boundary_description": "The compact play area is defined by the pedestrian zones of a historic district, including a vibrant street art district and a the plaza or square.",
|
| 3607 |
+
"boundary_landscape_tags_used": [
|
| 3608 |
+
"street_art_district",
|
| 3609 |
+
"historic_district",
|
| 3610 |
+
"plaza_or_square"
|
| 3611 |
+
],
|
| 3612 |
+
"arena_size_tier": "compact",
|
| 3613 |
+
"out_of_bounds": [
|
| 3614 |
+
"Beyond the designated pedestrian paths",
|
| 3615 |
+
"into active vehicle roadways",
|
| 3616 |
+
"inside private buildings"
|
| 3617 |
+
]
|
| 3618 |
+
},
|
| 3619 |
+
"safe_zones": [
|
| 3620 |
+
{
|
| 3621 |
+
"zone_id": "S01",
|
| 3622 |
+
"description": "The raised platform in the center of the plaza.",
|
| 3623 |
+
"landscape_tags_used": [
|
| 3624 |
+
"plaza_or_square"
|
| 3625 |
+
],
|
| 3626 |
+
"capacity_rule": "Max 1 player, 10-second limit.",
|
| 3627 |
+
"safety_flags": [
|
| 3628 |
+
"watch for uneven paving"
|
| 3629 |
+
]
|
| 3630 |
+
},
|
| 3631 |
+
{
|
| 3632 |
+
"zone_id": "S02",
|
| 3633 |
+
"description": "A designated alcove within the historic district, adorned with street art.",
|
| 3634 |
+
"landscape_tags_used": [
|
| 3635 |
+
"historic_district",
|
| 3636 |
+
"street_art_district"
|
| 3637 |
+
],
|
| 3638 |
+
"capacity_rule": "Max 1 player, 10-second limit.",
|
| 3639 |
+
"safety_flags": [
|
| 3640 |
+
"mind your step near obstacles"
|
| 3641 |
+
]
|
| 3642 |
+
}
|
| 3643 |
+
],
|
| 3644 |
+
"movement_features": {
|
| 3645 |
+
"chokepoints": [
|
| 3646 |
+
"Narrow passages and archways within the historic district",
|
| 3647 |
+
"alleyways decorated with street art",
|
| 3648 |
+
"entrances/exits to the plaza"
|
| 3649 |
+
],
|
| 3650 |
+
"open_zones": [
|
| 3651 |
+
"The wide open expanse of the plaza or square",
|
| 3652 |
+
"broader pedestrian streets in the street art district"
|
| 3653 |
+
],
|
| 3654 |
+
"hazard_zones": [
|
| 3655 |
+
"Uneven cobblestone paths and unexpected steps within the historic district",
|
| 3656 |
+
"blind corners in narrow alleys",
|
| 3657 |
+
"crowded areas near popular street art installations"
|
| 3658 |
+
]
|
| 3659 |
+
},
|
| 3660 |
+
"safety_constraints": {
|
| 3661 |
+
"exclusion_zones": [
|
| 3662 |
+
"private_property",
|
| 3663 |
+
"active_roadway",
|
| 3664 |
+
"construction_sites",
|
| 3665 |
+
"restricted_government_buildings"
|
| 3666 |
+
],
|
| 3667 |
+
"physical_limits": [
|
| 3668 |
+
"no grabbing or pushing — tag is an open-hand touch only",
|
| 3669 |
+
"no climbing",
|
| 3670 |
+
"no jumping",
|
| 3671 |
+
"no water entry",
|
| 3672 |
+
"no entering buildings",
|
| 3673 |
+
"no running across any roadway"
|
| 3674 |
+
],
|
| 3675 |
+
"collision_safety_note": "Players must be aware of other pedestrians and obstacles in the dense historic district. Exercise caution on uneven paving and steps, and navigate narrow alleys carefully due to blind corners and potential crowded sections.",
|
| 3676 |
+
"adult_supervision_required": true,
|
| 3677 |
+
"notes": "Temperate climate: no special constraints regarding temperature; however, players should stay hydrated. Be mindful of uneven surfaces and potential tripping hazards in the historic district."
|
| 3678 |
+
},
|
| 3679 |
+
"scoring": {
|
| 3680 |
+
"method": "survival_time",
|
| 3681 |
+
"winning_condition_detail": "The player who remains unfrozen for the longest cumulative time across all rounds wins the game."
|
| 3682 |
+
},
|
| 3683 |
+
"estimated_total_time_minutes": 60,
|
| 3684 |
+
"quality_score": 4.4
|
| 3685 |
+
}
|
| 3686 |
}
|
| 3687 |
]
|
scripts/scavenger_hunt/generator.py
CHANGED
|
@@ -21,10 +21,14 @@ Flags:
|
|
| 21 |
--seed INT fix random seed for sampling (optional)
|
| 22 |
--mock skip Gemini, use built-in stub (pipeline smoke-test)
|
| 23 |
"""
|
| 24 |
-
import os, re, json, time, random, logging, argparse
|
| 25 |
from sampler import sample_input, build_prompt
|
| 26 |
from validator import validate_example, auto_fix
|
| 27 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s")
|
| 29 |
log = logging.getLogger("cq")
|
| 30 |
|
|
@@ -100,10 +104,12 @@ def call_mock(prompt: str) -> str:
|
|
| 100 |
tasks = []
|
| 101 |
for i in range(n_tasks):
|
| 102 |
tag = tags[i % len(tags)]
|
|
|
|
|
|
|
| 103 |
tasks.append({
|
| 104 |
"task_id": f"T{i+1:02d}",
|
| 105 |
"title": f"Zone Study {i+1}",
|
| 106 |
-
"description":
|
| 107 |
"landscape_tags_used": [tag],
|
| 108 |
"task_type": types_cycle[i % len(types_cycle)],
|
| 109 |
"difficulty_contribution": diffs[i],
|
|
@@ -111,11 +117,11 @@ def call_mock(prompt: str) -> str:
|
|
| 111 |
"completion_proof": "photograph submitted via app",
|
| 112 |
"estimated_time_minutes": per_task,
|
| 113 |
"hints": {
|
| 114 |
-
"hint_1": "Scan the area from
|
| 115 |
-
"hint_2": f"Move toward
|
| 116 |
-
"hint_3": f"
|
| 117 |
},
|
| 118 |
-
"safety_flags":
|
| 119 |
})
|
| 120 |
|
| 121 |
bonus = (
|
|
@@ -163,138 +169,9 @@ def call_mock(prompt: str) -> str:
|
|
| 163 |
return json.dumps(out)
|
| 164 |
|
| 165 |
|
| 166 |
-
|
| 167 |
-
def generate_one(record: dict, backend, max_retries: int = 2):
|
| 168 |
-
"""
|
| 169 |
-
Try up to (max_retries+1) times.
|
| 170 |
-
On retry the validation errors are appended to the prompt so the model
|
| 171 |
-
knows exactly what to fix.
|
| 172 |
-
Returns (example_dict, None) on success, (None, [errors]) on failure.
|
| 173 |
-
"""
|
| 174 |
-
prompt = build_prompt(record)
|
| 175 |
-
last_errors = []
|
| 176 |
-
|
| 177 |
-
for attempt in range(max_retries + 1):
|
| 178 |
-
full_prompt = prompt
|
| 179 |
-
if attempt > 0 and last_errors:
|
| 180 |
-
full_prompt += (
|
| 181 |
-
"\n\nYOUR PREVIOUS ATTEMPT FAILED THESE VALIDATION CHECKS:\n"
|
| 182 |
-
+ "\n".join(f" • {e}" for e in last_errors[:8])
|
| 183 |
-
+ "\nPlease fix every item listed above. Return ONLY the corrected JSON."
|
| 184 |
-
)
|
| 185 |
-
|
| 186 |
-
try:
|
| 187 |
-
raw = backend(full_prompt)
|
| 188 |
-
output_json = extract_json(raw)
|
| 189 |
-
|
| 190 |
-
# the model sometimes wraps the output inside {"output": {...}}
|
| 191 |
-
if "output" in output_json and isinstance(output_json["output"], dict):
|
| 192 |
-
output_json = output_json["output"]
|
| 193 |
-
|
| 194 |
-
ex = {**record, "output": output_json}
|
| 195 |
-
ex = auto_fix(ex) # repair arithmetic first
|
| 196 |
-
ok, errors = validate_example(ex)
|
| 197 |
-
|
| 198 |
-
if ok:
|
| 199 |
-
return ex, None
|
| 200 |
-
last_errors = errors
|
| 201 |
-
log.warning(" attempt %d/%d — %d error(s): %s",
|
| 202 |
-
attempt + 1, max_retries + 1,
|
| 203 |
-
len(errors), errors[0])
|
| 204 |
-
|
| 205 |
-
except Exception as exc:
|
| 206 |
-
last_errors = [f"PARSE/API error: {exc}"]
|
| 207 |
-
log.warning(" attempt %d/%d — %s", attempt + 1, max_retries + 1, exc)
|
| 208 |
-
|
| 209 |
-
return None, last_errors
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
# ── main ──────────────────────────────────────────────────────────────────────
|
| 213 |
-
def main():
|
| 214 |
-
ap = argparse.ArgumentParser(description="CityQuest-AI dataset generator")
|
| 215 |
-
ap.add_argument("--n", type=int, default=10, help="examples to generate this run")
|
| 216 |
-
ap.add_argument("--output", default=DEFAULT_OUTPUT, help="output JSON file (resumes if exists)")
|
| 217 |
-
ap.add_argument("--sleep", type=float, default=8.0, help="seconds between Gemini calls")
|
| 218 |
-
ap.add_argument("--seed", type=int, default=None, help="random seed for reproducibility")
|
| 219 |
-
ap.add_argument("--mock", action="store_true", help="use stub backend (no API key needed)")
|
| 220 |
-
args = ap.parse_args()
|
| 221 |
-
|
| 222 |
-
if args.seed is not None:
|
| 223 |
-
random.seed(args.seed)
|
| 224 |
-
|
| 225 |
-
if not args.mock and not os.environ.get("GEMINI_API_KEY"):
|
| 226 |
-
log.error("GEMINI_API_KEY is not set.")
|
| 227 |
-
log.error(" Windows (PowerShell): $env:GEMINI_API_KEY = 'your-key'")
|
| 228 |
-
log.error(" Mac/Linux: export GEMINI_API_KEY='your-key'")
|
| 229 |
-
return
|
| 230 |
-
|
| 231 |
-
backend = call_mock if args.mock else call_gemini
|
| 232 |
-
|
| 233 |
-
out_dir = os.path.dirname(args.output)
|
| 234 |
-
if out_dir:
|
| 235 |
-
os.makedirs(out_dir, exist_ok=True)
|
| 236 |
-
|
| 237 |
-
# ── resume ────────────────────────────────────────────────────────────────
|
| 238 |
-
dataset = []
|
| 239 |
-
if os.path.exists(args.output):
|
| 240 |
-
try:
|
| 241 |
-
dataset = json.load(open(args.output, encoding="utf-8"))
|
| 242 |
-
log.info("Resuming — loaded %d existing examples from %s", len(dataset), args.output)
|
| 243 |
-
except json.JSONDecodeError:
|
| 244 |
-
log.warning("Existing output file unreadable; starting fresh.")
|
| 245 |
-
|
| 246 |
-
errors_path = args.output.replace(".json", "_errors.jsonl")
|
| 247 |
-
start_idx = len(dataset) + 1
|
| 248 |
-
kept = skipped = 0
|
| 249 |
-
quota_exhausted = False
|
| 250 |
-
|
| 251 |
-
# ── generation loop ───────────────────────────────────────────────────────
|
| 252 |
-
for i in range(start_idx, start_idx + args.n):
|
| 253 |
-
record = sample_input(i)
|
| 254 |
-
city = record["input"]["location"]["city"]
|
| 255 |
-
diff = record["input"]["preferences"]["difficulty"]
|
| 256 |
-
dur = record["input"]["preferences"]["duration_minutes"]
|
| 257 |
-
age = record["input"]["players"]["age_group"]
|
| 258 |
-
|
| 259 |
-
log.info("[%d/%d] %s | %-12s | %-6s | %3d min | %s",
|
| 260 |
-
i - start_idx + 1, args.n,
|
| 261 |
-
record["id"], city, diff, dur, age)
|
| 262 |
-
|
| 263 |
-
ex, errors = generate_one(record, backend)
|
| 264 |
-
|
| 265 |
-
if ex:
|
| 266 |
-
dataset.append(ex)
|
| 267 |
-
kept += 1
|
| 268 |
-
# incremental save — safe to interrupt at any point
|
| 269 |
-
with open(args.output, "w", encoding="utf-8") as f:
|
| 270 |
-
json.dump(dataset, f, indent=1, ensure_ascii=False)
|
| 271 |
-
log.info(" ✓ saved (dataset total: %d)", len(dataset))
|
| 272 |
-
else:
|
| 273 |
-
skipped += 1
|
| 274 |
-
with open(errors_path, "a", encoding="utf-8") as f:
|
| 275 |
-
f.write(json.dumps({"id": record["id"], "errors": errors}) + "\n")
|
| 276 |
-
log.info(" ✗ skipped after retries — logged to %s", errors_path)
|
| 277 |
-
|
| 278 |
-
if not args.mock and any("RESOURCE_EXHAUSTED" in e or "429" in e for e in errors):
|
| 279 |
-
log.warning(" quota exhausted on this API key — stopping run early")
|
| 280 |
-
quota_exhausted = True
|
| 281 |
-
break
|
| 282 |
-
|
| 283 |
-
if not args.mock:
|
| 284 |
-
time.sleep(args.sleep)
|
| 285 |
-
|
| 286 |
-
# ── summary ───────────────────────────────────────────────────────────────
|
| 287 |
-
log.info("")
|
| 288 |
-
log.info("══ DONE ══════════════════════════════════")
|
| 289 |
-
log.info(" Kept : %d", kept)
|
| 290 |
-
log.info(" Skipped : %d", skipped)
|
| 291 |
-
log.info(" Total : %d → %s", len(dataset), args.output)
|
| 292 |
-
if skipped:
|
| 293 |
-
log.info(" Failures: %s", errors_path)
|
| 294 |
-
|
| 295 |
-
if quota_exhausted:
|
| 296 |
-
raise SystemExit(2)
|
| 297 |
|
| 298 |
|
| 299 |
if __name__ == "__main__":
|
| 300 |
-
|
|
|
|
|
|
| 21 |
--seed INT fix random seed for sampling (optional)
|
| 22 |
--mock skip Gemini, use built-in stub (pipeline smoke-test)
|
| 23 |
"""
|
| 24 |
+
import os, re, json, time, random, logging, argparse, sys
|
| 25 |
from sampler import sample_input, build_prompt
|
| 26 |
from validator import validate_example, auto_fix
|
| 27 |
|
| 28 |
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 29 |
+
sys.path.insert(0, os.path.join(HERE, "..", "common"))
|
| 30 |
+
from cq_common import has_risk_indicators
|
| 31 |
+
|
| 32 |
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s")
|
| 33 |
log = logging.getLogger("cq")
|
| 34 |
|
|
|
|
| 104 |
tasks = []
|
| 105 |
for i in range(n_tasks):
|
| 106 |
tag = tags[i % len(tags)]
|
| 107 |
+
description = f"Within the zone characterised by {tag.replace('_',' ')}, locate and document the most structurally distinct feature visible from a public pedestrian path."
|
| 108 |
+
flags = ["uneven paving may be slippery when wet", "watch for cyclists sharing the path"] if has_risk_indicators(description) else []
|
| 109 |
tasks.append({
|
| 110 |
"task_id": f"T{i+1:02d}",
|
| 111 |
"title": f"Zone Study {i+1}",
|
| 112 |
+
"description": description,
|
| 113 |
"landscape_tags_used": [tag],
|
| 114 |
"task_type": types_cycle[i % len(types_cycle)],
|
| 115 |
"difficulty_contribution": diffs[i],
|
|
|
|
| 117 |
"completion_proof": "photograph submitted via app",
|
| 118 |
"estimated_time_minutes": per_task,
|
| 119 |
"hints": {
|
| 120 |
+
"hint_1": "Scan the area from open space.",
|
| 121 |
+
"hint_2": f"Move toward the {tag.replace('_',' ')} zone and compare features.",
|
| 122 |
+
"hint_3": f"Within the {tag.replace('_',' ')} zone, find the feature that differs most from its neighbours in scale or colour.",
|
| 123 |
},
|
| 124 |
+
"safety_flags": flags,
|
| 125 |
})
|
| 126 |
|
| 127 |
bonus = (
|
|
|
|
| 169 |
return json.dumps(out)
|
| 170 |
|
| 171 |
|
| 172 |
+
from cli import run_cli # noqa: E402
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 173 |
|
| 174 |
|
| 175 |
if __name__ == "__main__":
|
| 176 |
+
run_cli("scavenger_hunt", sample_input, build_prompt, auto_fix,
|
| 177 |
+
validate_example, DEFAULT_OUTPUT, call_mock)
|
scripts/scavenger_hunt/sampler.py
CHANGED
|
@@ -145,20 +145,28 @@ climate note : {climate_note_txt}
|
|
| 145 |
|
| 146 |
═══ ABSOLUTE RULES ═══
|
| 147 |
1. NO proper nouns anywhere — no city names, landmark names, street names, brand names.
|
| 148 |
-
Tasks must work purely from the landscape_tags.
|
| 149 |
-
|
| 150 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
|
| 152 |
2. Each task's landscape_tags_used must be a strict subset of: {json.dumps(loc["landscape_tags"])}
|
| 153 |
|
| 154 |
3. Use at least 3 different task_type values from:
|
| 155 |
{json.dumps(TASK_TYPES)}
|
| 156 |
|
| 157 |
-
4. Hints per task:
|
| 158 |
-
hint_1 =
|
| 159 |
-
hint_2 =
|
| 160 |
-
hint_3 =
|
| 161 |
All three hints must be meaningfully distinct from each other.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
|
| 163 |
5. No task may require: climbing, jumping, entering water, entering any building
|
| 164 |
(exception: social_interaction tasks may enter public-facing shops).
|
|
@@ -167,6 +175,16 @@ climate note : {climate_note_txt}
|
|
| 167 |
|
| 168 |
7. {bonus_instruction}
|
| 169 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
═══ OUTPUT SHAPE ═══
|
| 171 |
{{
|
| 172 |
"game_title": "string",
|
|
|
|
| 145 |
|
| 146 |
═══ ABSOLUTE RULES ═══
|
| 147 |
1. NO proper nouns anywhere — no city names, landmark names, street names, brand names.
|
| 148 |
+
Tasks must work purely from the landscape_tags.
|
| 149 |
+
ALSO BANNED everywhere (description, title, hints, completion_proof — these leak
|
| 150 |
+
city-specific knowledge the player shouldn't need): "central", "main", "largest",
|
| 151 |
+
"most famous", "the famous", "best-known", "best known", "downtown", and "iconic"
|
| 152 |
+
(unless followed by the word "landmark", e.g. "iconic landmark" is fine).
|
| 153 |
+
✓ GOOD: "Find the tallest structure visible from the open square and photograph its base."
|
| 154 |
+
✗ BAD: "Find the Eiffel Tower." / "...the main square..." / "...the most famous fountain..."
|
| 155 |
|
| 156 |
2. Each task's landscape_tags_used must be a strict subset of: {json.dumps(loc["landscape_tags"])}
|
| 157 |
|
| 158 |
3. Use at least 3 different task_type values from:
|
| 159 |
{json.dumps(TASK_TYPES)}
|
| 160 |
|
| 161 |
+
4. Hints per task — HARD character limits (count characters, not words):
|
| 162 |
+
hint_1 = a short directional nudge, AT MOST 50 characters total.
|
| 163 |
+
hint_2 = describes the feature to look for, AT MOST 80 characters total, longer than hint_1.
|
| 164 |
+
hint_3 = a near-explicit walkthrough, AT MOST 120 characters total, longer than hint_2.
|
| 165 |
All three hints must be meaningfully distinct from each other.
|
| 166 |
+
hint_3 must NOT use spoiler phrases that give away exact spatial position relative to
|
| 167 |
+
another object, e.g. avoid "just before", "next to", "to the left of", "to the right of",
|
| 168 |
+
"near the corner of", "across from", "adjacent to", "directly behind", "right after",
|
| 169 |
+
"just past". Describe the feature itself instead of its position relative to something else.
|
| 170 |
|
| 171 |
5. No task may require: climbing, jumping, entering water, entering any building
|
| 172 |
(exception: social_interaction tasks may enter public-facing shops).
|
|
|
|
| 175 |
|
| 176 |
7. {bonus_instruction}
|
| 177 |
|
| 178 |
+
8. social_interaction tasks: completion_proof must require a photo, receipt, or other
|
| 179 |
+
physical/digital artifact — NEVER a verbal-only response (no "tell us", "ask them",
|
| 180 |
+
"verbal answer", etc.).
|
| 181 |
+
|
| 182 |
+
9. If a task's landscape_tags_used or description touches water, elevation/slopes/stairs,
|
| 183 |
+
alleys/narrow passages, crowded areas, or dark/low-light areas, that task's safety_flags
|
| 184 |
+
must contain AT LEAST 2 specific hazard mentions (e.g. naming the slippery surface, the
|
| 185 |
+
blind corner, the steep step, the low visibility) — not generic phrases like "be careful"
|
| 186 |
+
or "stay safe".
|
| 187 |
+
|
| 188 |
═══ OUTPUT SHAPE ═══
|
| 189 |
{{
|
| 190 |
"game_title": "string",
|
scripts/scavenger_hunt/test_pipeline.py
CHANGED
|
@@ -49,43 +49,43 @@ GOOD = {
|
|
| 49 |
"tasks": [
|
| 50 |
{
|
| 51 |
"task_id": "T01", "title": "Square Census",
|
| 52 |
-
"description": "
|
| 53 |
"landscape_tags_used": ["plaza_or_square"],
|
| 54 |
"task_type": "observe_and_answer",
|
| 55 |
"difficulty_contribution": "easy", "points": 10,
|
| 56 |
-
"completion_proof": "
|
| 57 |
"hints": {
|
| 58 |
-
"hint_1": "Find
|
| 59 |
-
"hint_2": "Open
|
| 60 |
-
"hint_3": "
|
| 61 |
},
|
| 62 |
"safety_flags": [],
|
| 63 |
},
|
| 64 |
{
|
| 65 |
"task_id": "T02", "title": "Vendor Colours",
|
| 66 |
-
"description": "At
|
| 67 |
"landscape_tags_used": ["market_outdoor"],
|
| 68 |
"task_type": "find_and_photograph",
|
| 69 |
"difficulty_contribution": "easy", "points": 10,
|
| 70 |
"completion_proof": "photo", "estimated_time_minutes": 12,
|
| 71 |
"hints": {
|
| 72 |
-
"hint_1": "Follow
|
| 73 |
-
"hint_2": "Open-air markets
|
| 74 |
-
"hint_3": "
|
| 75 |
},
|
| 76 |
"safety_flags": [],
|
| 77 |
},
|
| 78 |
{
|
| 79 |
"task_id": "T03", "title": "Tallest Sighting",
|
| 80 |
-
"description": "Find a spot where
|
| 81 |
"landscape_tags_used": ["iconic_landmark"],
|
| 82 |
"task_type": "reach_and_verify",
|
| 83 |
"difficulty_contribution": "easy", "points": 10,
|
| 84 |
"completion_proof": "photo", "estimated_time_minutes": 14,
|
| 85 |
"hints": {
|
| 86 |
-
"hint_1": "Look up from
|
| 87 |
-
"hint_2": "Tall structures are
|
| 88 |
-
"hint_3": "
|
| 89 |
},
|
| 90 |
"safety_flags": [],
|
| 91 |
},
|
|
@@ -154,6 +154,29 @@ bad = auto_fix(bad)
|
|
| 154 |
ok, errs = validate_example(bad)
|
| 155 |
check("T6: time overrun caught", not ok and any("TIME" in e for e in errs))
|
| 156 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
# ── 7: sampler variety ────────────────────────────────────────────────────────
|
| 158 |
print("\n── Sampler tests ─────────────────────────────────────")
|
| 159 |
samples = [sample_input(i, rng=random.Random(i)) for i in range(1, 21)]
|
|
|
|
| 49 |
"tasks": [
|
| 50 |
{
|
| 51 |
"task_id": "T01", "title": "Square Census",
|
| 52 |
+
"description": "Within the plaza area, count the benches visible while standing in one spot without moving.",
|
| 53 |
"landscape_tags_used": ["plaza_or_square"],
|
| 54 |
"task_type": "observe_and_answer",
|
| 55 |
"difficulty_contribution": "easy", "points": 10,
|
| 56 |
+
"completion_proof": "photo of the bench count tally", "estimated_time_minutes": 10,
|
| 57 |
"hints": {
|
| 58 |
+
"hint_1": "Find an open paved square.",
|
| 59 |
+
"hint_2": "Open squares have multiple pedestrian paths converging toward open paving.",
|
| 60 |
+
"hint_3": "Stand where several pedestrian paths meet in the open paving, rotate slowly, and count every fixed seat in view.",
|
| 61 |
},
|
| 62 |
"safety_flags": [],
|
| 63 |
},
|
| 64 |
{
|
| 65 |
"task_id": "T02", "title": "Vendor Colours",
|
| 66 |
+
"description": "At the open-air market area, photograph three stalls whose canopies show three different colours.",
|
| 67 |
"landscape_tags_used": ["market_outdoor"],
|
| 68 |
"task_type": "find_and_photograph",
|
| 69 |
"difficulty_contribution": "easy", "points": 10,
|
| 70 |
"completion_proof": "photo", "estimated_time_minutes": 12,
|
| 71 |
"hints": {
|
| 72 |
+
"hint_1": "Follow sounds of trade and stalls.",
|
| 73 |
+
"hint_2": "Open-air markets line pedestrian streets with rows of canopied stalls.",
|
| 74 |
+
"hint_3": "Walk one full row of stalls end to end and photograph the first red, blue, and white canopy seen.",
|
| 75 |
},
|
| 76 |
"safety_flags": [],
|
| 77 |
},
|
| 78 |
{
|
| 79 |
"task_id": "T03", "title": "Tallest Sighting",
|
| 80 |
+
"description": "Find a spot where a tall structure is fully visible and photograph it with a teammate in frame.",
|
| 81 |
"landscape_tags_used": ["iconic_landmark"],
|
| 82 |
"task_type": "reach_and_verify",
|
| 83 |
"difficulty_contribution": "easy", "points": 10,
|
| 84 |
"completion_proof": "photo", "estimated_time_minutes": 14,
|
| 85 |
"hints": {
|
| 86 |
+
"hint_1": "Look up from open ground nearby.",
|
| 87 |
+
"hint_2": "Tall structures are visible from open areas with no overhead blockage.",
|
| 88 |
+
"hint_3": "Stand in open paving facing where rooftops are lowest, then photograph the tall structure with a teammate in view.",
|
| 89 |
},
|
| 90 |
"safety_flags": [],
|
| 91 |
},
|
|
|
|
| 154 |
ok, errs = validate_example(bad)
|
| 155 |
check("T6: time overrun caught", not ok and any("TIME" in e for e in errs))
|
| 156 |
|
| 157 |
+
# ── audit-fix failing fixtures ─────────────────────────────────────────────────
|
| 158 |
+
bad = copy.deepcopy(GOOD)
|
| 159 |
+
bad["output"]["tasks"][0]["description"] = "Within the main plaza area, count the benches visible while standing in one spot without moving."
|
| 160 |
+
ok, errs = validate_example(bad)
|
| 161 |
+
check("T11: soft city-leakage word 'main' caught", not ok and any("LEAK" in e for e in errs), errs)
|
| 162 |
+
|
| 163 |
+
bad = copy.deepcopy(GOOD)
|
| 164 |
+
bad["output"]["tasks"][0]["task_type"] = "social_interaction"
|
| 165 |
+
bad["output"]["tasks"][0]["completion_proof"] = "ask a vendor and tell us their answer"
|
| 166 |
+
ok, errs = validate_example(bad)
|
| 167 |
+
check("T12: verbal-only social_interaction proof caught", not ok and any("PROOF" in e for e in errs), errs)
|
| 168 |
+
|
| 169 |
+
bad = copy.deepcopy(GOOD)
|
| 170 |
+
bad["output"]["tasks"][0]["description"] = "Walk along the water's edge near the alley and count the boats moored there."
|
| 171 |
+
bad["output"]["tasks"][0]["safety_flags"] = []
|
| 172 |
+
ok, errs = validate_example(bad)
|
| 173 |
+
check("T13: water/alley task with <2 safety_flags caught", not ok and any("SAFETY" in e for e in errs), errs)
|
| 174 |
+
|
| 175 |
+
bad = copy.deepcopy(GOOD)
|
| 176 |
+
bad["output"]["tasks"][0]["hints"]["hint_3"] = "Walk to the plaza entrance, and the bench cluster is just before the fountain, next to the lamppost on the left side path."
|
| 177 |
+
ok, errs = validate_example(bad)
|
| 178 |
+
check("T14: hint_3 spoiler/length violation caught", not ok and any("HINTSPOILER" in e or "HINTLEN" in e for e in errs), errs)
|
| 179 |
+
|
| 180 |
# ── 7: sampler variety ────────────────────────────────────────────────────────
|
| 181 |
print("\n── Sampler tests ─────────────────────────────────────")
|
| 182 |
samples = [sample_input(i, rng=random.Random(i)) for i in range(1, 21)]
|
scripts/scavenger_hunt/validator.py
CHANGED
|
@@ -4,12 +4,17 @@ Validates one training example. Returns (is_valid: bool, errors: list[str]).
|
|
| 4 |
auto_fix() recomputes arithmetic fields in-place — never reject for math the model shouldn't be trusted with.
|
| 5 |
"""
|
| 6 |
import os
|
|
|
|
| 7 |
import sys
|
| 8 |
from Levenshtein import distance as lev
|
| 9 |
|
| 10 |
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 11 |
sys.path.insert(0, os.path.join(HERE, "..", "common"))
|
| 12 |
-
from cq_common import
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
POINTS = {"easy": 10, "medium": 20, "hard": 30}
|
| 15 |
|
|
@@ -52,6 +57,34 @@ def validate_example(ex: dict) -> tuple[bool, list[str]]:
|
|
| 52 |
if found:
|
| 53 |
errors.append(f"NOUN: {tid} {hk} contains {set(found)}")
|
| 54 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
# tags used must be subset of input tags
|
| 56 |
used = set(t.get("landscape_tags_used",[]))
|
| 57 |
extra = used - input_tags
|
|
@@ -126,14 +159,91 @@ def validate_example(ex: dict) -> tuple[bool, list[str]]:
|
|
| 126 |
return len(errors) == 0, errors
|
| 127 |
|
| 128 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
def auto_fix(ex: dict) -> dict:
|
| 130 |
"""Recompute all arithmetic output fields in-place. Call before validate_example."""
|
| 131 |
try:
|
|
|
|
|
|
|
| 132 |
tasks = ex["output"]["tasks"]
|
| 133 |
for t in tasks:
|
| 134 |
d = t.get("difficulty_contribution")
|
| 135 |
if d in POINTS:
|
| 136 |
t["points"] = POINTS[d]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
base = sum(t["points"] for t in tasks)
|
| 138 |
bonus_pts = (ex["output"].get("bonus_task") or {}).get("points") or 0
|
| 139 |
ex["output"]["task_count"] = len(tasks)
|
|
|
|
| 4 |
auto_fix() recomputes arithmetic fields in-place — never reject for math the model shouldn't be trusted with.
|
| 5 |
"""
|
| 6 |
import os
|
| 7 |
+
import re as _re
|
| 8 |
import sys
|
| 9 |
from Levenshtein import distance as lev
|
| 10 |
|
| 11 |
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 12 |
sys.path.insert(0, os.path.join(HERE, "..", "common"))
|
| 13 |
+
from cq_common import (
|
| 14 |
+
VALID_TAGS, nouns_in as _nouns_in, soft_leaks_in,
|
| 15 |
+
is_verbal_only_proof, has_risk_indicators, specific_flag_count,
|
| 16 |
+
HINT_LIMITS, hint_spoilers_in, fix_text_leaks,
|
| 17 |
+
)
|
| 18 |
|
| 19 |
POINTS = {"easy": 10, "medium": 20, "hard": 30}
|
| 20 |
|
|
|
|
| 57 |
if found:
|
| 58 |
errors.append(f"NOUN: {tid} {hk} contains {set(found)}")
|
| 59 |
|
| 60 |
+
# soft city-leakage words (audit §1) — description + hints
|
| 61 |
+
leaks = soft_leaks_in(t.get("description",""))
|
| 62 |
+
if leaks:
|
| 63 |
+
errors.append(f"LEAK: {tid} description contains city-knowledge words {set(leaks)}")
|
| 64 |
+
for hk in ("hint_1","hint_2","hint_3"):
|
| 65 |
+
leaks = soft_leaks_in(t.get("hints",{}).get(hk,""))
|
| 66 |
+
if leaks:
|
| 67 |
+
errors.append(f"LEAK: {tid} {hk} contains city-knowledge words {set(leaks)}")
|
| 68 |
+
|
| 69 |
+
# hint length limits + anti-spoiler check (audit §4)
|
| 70 |
+
for hk, limit in HINT_LIMITS.items():
|
| 71 |
+
h = t.get("hints",{}).get(hk,"")
|
| 72 |
+
if len(h) > limit:
|
| 73 |
+
errors.append(f"HINTLEN: {tid} {hk} is {len(h)} chars (max {limit})")
|
| 74 |
+
spoilers = hint_spoilers_in(t.get("hints",{}).get("hint_3",""))
|
| 75 |
+
if spoilers:
|
| 76 |
+
errors.append(f"HINTSPOILER: {tid} hint_3 contains direction give-aways {spoilers}")
|
| 77 |
+
|
| 78 |
+
# verifiable proof for social_interaction tasks (audit §2)
|
| 79 |
+
if t.get("task_type") == "social_interaction" and is_verbal_only_proof(t.get("completion_proof","")):
|
| 80 |
+
errors.append(f"PROOF: {tid} social_interaction completion_proof is verbal-only, needs photo/receipt")
|
| 81 |
+
|
| 82 |
+
# safety flag specificity (audit §3)
|
| 83 |
+
risky_text = " ".join([t.get("description",""), t.get("title","")])
|
| 84 |
+
if has_risk_indicators(risky_text):
|
| 85 |
+
if specific_flag_count(t.get("safety_flags")) < 2:
|
| 86 |
+
errors.append(f"SAFETY: {tid} touches water/elevation/alley/crowd/dark but has <2 specific safety_flags")
|
| 87 |
+
|
| 88 |
# tags used must be subset of input tags
|
| 89 |
used = set(t.get("landscape_tags_used",[]))
|
| 90 |
extra = used - input_tags
|
|
|
|
| 159 |
return len(errors) == 0, errors
|
| 160 |
|
| 161 |
|
| 162 |
+
_LEAK_REPLACEMENTS = {
|
| 163 |
+
"most famous": "most distinctive",
|
| 164 |
+
"the famous": "the notable",
|
| 165 |
+
"best-known": "most noticeable",
|
| 166 |
+
"best known": "most noticeable",
|
| 167 |
+
"central": "the",
|
| 168 |
+
"downtown": "this area",
|
| 169 |
+
"largest": "biggest",
|
| 170 |
+
"main": "the",
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
_SPOILER_PHRASES = [
|
| 174 |
+
"just before", "next to", "to the left of", "to the right of",
|
| 175 |
+
"near the corner of", "across from", "adjacent to",
|
| 176 |
+
"directly behind", "right after", "just past",
|
| 177 |
+
]
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
def _strip_leaks(text: str) -> str:
|
| 181 |
+
if not text:
|
| 182 |
+
return text
|
| 183 |
+
for phrase, repl in _LEAK_REPLACEMENTS.items():
|
| 184 |
+
text = _re.sub(r"\b" + _re.escape(phrase) + r"\b", repl, text, flags=_re.IGNORECASE)
|
| 185 |
+
text = _re.sub(r"\biconic\b(?!\s+landmark)", "notable", text, flags=_re.IGNORECASE)
|
| 186 |
+
text = _re.sub(r"\bthe the\b", "the", text, flags=_re.IGNORECASE)
|
| 187 |
+
return _re.sub(r"\s{2,}", " ", text).strip()
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
def _strip_spoilers(text: str) -> str:
|
| 191 |
+
if not text:
|
| 192 |
+
return text
|
| 193 |
+
for phrase in _SPOILER_PHRASES:
|
| 194 |
+
text = _re.sub(_re.escape(phrase), "near", text, flags=_re.IGNORECASE)
|
| 195 |
+
return _re.sub(r"\s{2,}", " ", text).strip()
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
def _truncate(text: str, limit: int) -> str:
|
| 199 |
+
if not text or len(text) <= limit:
|
| 200 |
+
return text
|
| 201 |
+
cut = text[:limit]
|
| 202 |
+
if " " in cut:
|
| 203 |
+
cut = cut[: cut.rfind(" ")]
|
| 204 |
+
return cut.rstrip(" ,.;:")
|
| 205 |
+
|
| 206 |
+
|
| 207 |
def auto_fix(ex: dict) -> dict:
|
| 208 |
"""Recompute all arithmetic output fields in-place. Call before validate_example."""
|
| 209 |
try:
|
| 210 |
+
loc = ex["input"]["location"]
|
| 211 |
+
ex["output"]["game_title"] = fix_text_leaks(ex["output"].get("game_title", ""), [loc.get("city"), loc.get("country")])
|
| 212 |
tasks = ex["output"]["tasks"]
|
| 213 |
for t in tasks:
|
| 214 |
d = t.get("difficulty_contribution")
|
| 215 |
if d in POINTS:
|
| 216 |
t["points"] = POINTS[d]
|
| 217 |
+
|
| 218 |
+
# strip soft city-leakage words from description (audit §1)
|
| 219 |
+
t["description"] = _strip_leaks(t.get("description", ""))
|
| 220 |
+
|
| 221 |
+
# clean + enforce hint limits / anti-spoiler (audit §4)
|
| 222 |
+
hints = t.get("hints")
|
| 223 |
+
if isinstance(hints, dict):
|
| 224 |
+
for hk in ("hint_1", "hint_2", "hint_3"):
|
| 225 |
+
if hk in hints and isinstance(hints[hk], str):
|
| 226 |
+
hints[hk] = _strip_leaks(hints[hk])
|
| 227 |
+
if "hint_3" in hints and isinstance(hints["hint_3"], str):
|
| 228 |
+
hints["hint_3"] = _strip_spoilers(hints["hint_3"])
|
| 229 |
+
for hk, limit in HINT_LIMITS.items():
|
| 230 |
+
if hk in hints and isinstance(hints[hk], str):
|
| 231 |
+
hints[hk] = _truncate(hints[hk], limit)
|
| 232 |
+
|
| 233 |
+
# ensure ≥2 specific safety_flags for risky tasks (audit §3)
|
| 234 |
+
risky_text = " ".join([t.get("description", ""), t.get("title", "")])
|
| 235 |
+
if has_risk_indicators(risky_text):
|
| 236 |
+
flags = t.get("safety_flags") or []
|
| 237 |
+
if specific_flag_count(flags) < 2:
|
| 238 |
+
extra = [
|
| 239 |
+
"watch your footing — surfaces near here can be uneven or slippery",
|
| 240 |
+
"stay alert for blind corners and limited visibility around this spot",
|
| 241 |
+
]
|
| 242 |
+
for e in extra:
|
| 243 |
+
if specific_flag_count(flags) >= 2:
|
| 244 |
+
break
|
| 245 |
+
flags.append(e)
|
| 246 |
+
t["safety_flags"] = flags
|
| 247 |
base = sum(t["points"] for t in tasks)
|
| 248 |
bonus_pts = (ex["output"].get("bonus_task") or {}).get("points") or 0
|
| 249 |
ex["output"]["task_count"] = len(tasks)
|